# JSON module – parse JSON files

A basic JSON parser

Beastie can output JSON in some cases, and for this or other reasons, it's sometimes useful to be able to parse JSON to Scheme s-expressions.

The goal here is to parse valid JSON correctly, as described in RFC 8259. The parser is strict, and will not try to recover from syntax errors. We pass all of the `y_` and `n_` tests at this test suite. We implement no extensions.

The only parsing wrinkle is that, since JSON arrays are mapped to s7 hash-tables with symbol keys, the JSON object `{"": 0}` (ie, with a zero-length key) would map to a hash-table entry with a zero-length symbol name. s7 manages to cope with that, but it's unexpected and inconvenient, so this is special-cased to a key of `'_` instead. This choice also incidentally means that any duplicate keys in the JSON will be resolved into a single key in the hash-table, in an unspecified way.

We assume the input is UTF-8; this is required by the current RFC, but was not historically true of JSON.

## Functions

Index:

- json-write!
- parse-json-file
- parse-json-string

### json-write!

`(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.

### parse-json-file

`(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.

### parse-json-string

`(parse-json-string str [:on-error func])` : similar to parse-json-file.

Norman
2026 August 02