Skip to content

RFC 8259 Scope

This page defines the JSON accepted and rejected by cl-json-kit.

Conformance results

The vendored JSONTestSuite corpus runs in CI:

Corpus class Requirement Cases Result
y_ every conforming parser must accept 95 95 accepted
n_ every conforming parser must reject 176 + 12 all rejected
i_ implementation-defined 22 + 13 each choice pinned by a test

No case crashes, hangs, or signals anything other than json-parse-error; the two pathological cases the corpus ships as 100 KB and 250 KB files of repeated punctuation are rejected by a depth bound rather than by exhausting the control stack.

The counts split because 25 cases (the + 12 and + 13 above) carry input that is not well-formed UTF-8 — lone continuation bytes, overlong sequences, UTF-16 with and without a BOM, ISO-8859-1. This library's API consumes Common Lisp characters rather than octets (see Characters, not octets below), so those inputs are rejected by the external-format layer before the reader sees them, and the vendored tests exclude them by name.

The implementation-defined cases are where parsers legitimately disagree — deeply nested arrays, numbers outside double-float range, lone surrogates in \uXXXX escapes. Each is listed in the test file together with this library's choice.

What the reader accepts

The reader accepts one complete RFC 8259 JSON text:

  • objects, arrays, strings, numbers, true, false, and null;
  • only JSON-defined whitespace;
  • string escapes, including UTF-16 surrogate-pair combination for characters outside the Basic Multilingual Plane.

What the reader rejects

  • unescaped control characters in strings;
  • malformed escapes;
  • lone (unpaired) surrogates;
  • invalid number forms;
  • a leading UTF-8 byte-order mark (U+FEFF) — it is not JSON-defined whitespace, so it is rejected like any other unexpected character; strip it yourself first (see the FAQ and Recipes);
  • trailing data after the value (for parse; parse-prefix and read-json intentionally stop at the value boundary).

Numbers

The built-in decoder maps ordinary non-integer numbers to double-float. When float conversion overflows or underflows, it falls back to an exact integer or ratio instead of rejecting an otherwise valid JSON number; for example, 1e400 remains an exact integer. max-exact-exponent bounds the absolute decimal scale used by that fallback, preventing an untrusted exponent from requesting an enormous expt computation.

Supply a number-decoder when the application needs its own decimal type or a uniform number representation, and a number-encoder to control formatting on output.

Characters, not octets

The API operates on Common Lisp characters, not octets. Encoding validity and UTF-8 decoding errors therefore belong to the stream / external-format layer — configure your stream before calling read-json or write-json.

The writer emits non-ASCII Lisp characters directly and escapes the required JSON characters; it does not promise ASCII-only output.

Non-goals

The following are explicitly out of scope:

  • RFC 8785 canonicalization (canonical number formatting, byte-for-byte cross-implementation output);
  • multiple-value stream framing beyond what parse-prefix / read-json provide;
  • comments;
  • trailing commas;
  • JSON5 and other extensions;
  • arbitrary-precision decimal preservation without a custom number-decoder / number-encoder;
  • an octet / UTF-8 API.

:sort-keys t therefore yields deterministic member ordering, not RFC 8785 canonical JSON. See Writing JSON.