;; Functions supporting (basic) json-file parsing ;; ;; This file is part of Beastie ;; SPDX-FileCopyrightText: 2025 Norman Gray ;; SPDX-License-Identifier: BSD-2-Clause (define *requires-implementation-functions* '(parse-json-source**)) (define-macro (%module-verbosity-flag%) 256) (define/provide* (parse-json-file fn (on-error (λ (msg) (print-warning msg) #f))) #"""`(parse-json-file filename [:on-error func])` : parse the contents of a file containing JSON, and return as a sexp; if the filename is `#f`, then parse stdin. On any parsing error, the function calls the `on-error` procedure, passing it an error message. The default procedure prints the message as a warning and returns `#f`. Since `false` is a legitimate JSON input, this also would produce `#f` as output: if it is necessary to distinguish an error from a successful parse of `false`, then a non-default `on-error` procedure can do so. Numbers are reported as floats, strings as `ustring?`, lists as lists, objects as hash-tables, and `false`, `true` and `null` as `#f`, `#t` and `()` respectively. See RFC 8259. This is a strict parser, and will object to any malformed input. We don't recognise any extensions, such as comments in the file.""" (let ((res (parse-json-source** #t fn))) (if (car res) (cdr res) (on-error (cdr res))))) (define/provide* (parse-json-string str (on-error (λ (msg) (print-warning msg) #f))) "`(parse-json-string str [:on-error func])` : similar to parse-json-file." (let ((res (parse-json-source** #f str))) (if (car res) (cdr res) (on-error (cdr res))))) (define (json-write*! j indent) (define (write-with-seps* j start mid end) (let loop ((l j) (sep start)) (if (null? l) (display end) (begin (display sep) (when (char=? (string-ref sep (- (length sep) 1)) #\newline) (let loop ((n indent)) (when (> n 0) (display " ") (loop (- n 1))))) (json-write*! (car l) (+ indent 1)) (loop (cdr l) mid))))) (cond ((or (string? j) (number? j)) (write j)) ((symbol? j) (printf "\"~a\"" j)) ((and (pair? j) (symbol? (car j))) (json-write*! (car j) (+ indent 1)) (display ": ") (json-write*! (cdr j) (+ indent 1))) ((hash-table? j) (write-with-seps* (map values j) "{\n" ",\n" "}")) ((null? j) (display "null")) ((boolean? j) (display (if j "true" "false"))) ((list? j) (cond ((null? j) (printf "[]")) ((every (λ (p) (and (pair? p) (symbol? (car p)))) j) ;; alist ;; sort the keys, to make this reproducible/testable (write-with-seps* (sort! j (λ (a b) (symbollist j) "[\n" ",\n" "]")) (else (eprintf "json-write!: unexpected object type ~s; giving it a go...~%" j) (display j)))) (define/provide (json-write! j) #"""`(json-write! sexp)` : write the sexp as JSON, to the `(current-output-port)`. Mapping to output: * numbers are written as JSON numbers * strings and symbols are written as JSON strings * hash-tables and alists are written as JSON objects * non-empty lists, and vectors, are written as JSON lists * `#f`, `#t`, and `()` are written as JSON `false`, `true`, and `null`, respectively. Because multiple Scheme structures are written to the small range of JSON types, it's not possible in general to round-trip structures via JSON.""" (json-write*! j 1) #)