Skip to content

RFC 8259 Scope

This page states precisely what cl-json-kit accepts, what it rejects, and what is explicitly out of scope.

Conformance results

Conformance is measured, not asserted. The whole parsing corpus of JSONTestSuite — the reference suite behind Parsing JSON is a Minefield — is vendored into t/rfc8259-conformance-test.lisp and runs on every CI build, so these numbers are a regression gate rather than a one-off measurement:

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 rather than claim credit for them.

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, so a silent change in any of them fails the build.

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.