Contributing¶
Contributions are welcome. This page covers the development workflow, how to run the tests and benchmarks, and the conventions the codebase follows.
Development environment¶
The repository is a Nix flake. The simplest way to get a working toolchain is:
This drops you into a shell with SBCL and the competitor JSON libraries
(Jzon, Jonathan, JSOWN, Yason) that the benchmarks compare
against. If you use direnv, direnv allow loads it
automatically.
If you prefer a local SBCL, ensure cl-json-kit (and, for the tests,
cl-weave) are visible to ASDF, then load the system:
Running the tests¶
From a checkout:
Or reproducibly, as a Nix derivation (this is what CI runs):
The test system (cl-json-kit/test) uses cl-weave and lives under t/. It
includes a property-based fuzzing suite over parse.
Two files there guard the project's standing promises rather than any one feature, and are worth knowing about before changing them:
t/public-api-test.lisppins the exact set of symbols thejson-kitpackage exports and requires a docstring on each. Adding or removing an export fails this spec by design — update the list in the same commit, and classify the change against the Compatibility Promise.t/rfc8259-conformance-test.lispvendors the parsing corpus of JSONTestSuite (MIT) as data. It is vendored rather than fetched so conformance is checked offline inside the Nix sandbox. Each case is stored as a list of ASCII string chunks and character codes, which keeps the file pure ASCII and makes every code point explicit instead of dependent on the external format used to load the source. The 25 corpus cases whose input is not well-formed UTF-8 are listed by name in the file's header and deliberately excluded, because this library's API consumes characters rather than octets.
Running the benchmarks¶
See Benchmarks for the full harness documentation. In short:
# The library's own reader/writer throughput.
sbcl --noinform --disable-debugger --script benchmark/run.lisp > results.tsv
# Comparison against other libraries (needs the nix develop shell).
nix develop --command sbcl --noinform --disable-debugger \
--script benchmark/competitors.lisp > competitor-results.tsv
Source layout¶
The reader and writer are split into small, per-concern files, loaded serially
by cl-json-kit.asd:
| Area | Files |
|---|---|
| Package and shared data | src/package.lisp, src/data.lisp |
| Shared control flow | src/reader-macros.lisp, src/writer-macros.lisp |
| Conditions | src/conditions.lisp |
| Reader | src/parser-state.lisp, src/reader-strings.lisp, src/reader-numbers.lisp, src/reader-collections.lisp, src/reader.lisp, src/reader-stream.lisp |
| Writer | src/writer-state.lisp, src/writer-scalars.lisp, src/writer-collections.lisp, src/writer.lisp |
| Conversion | src/conversion.lisp |
The single public package json-kit is defined in src/package.lisp; only the
symbols listed there are part of the supported API. See the
API Reference.
Conventions¶
- Explicit shape, always. The library never infers JSON object/array shape from the structure of a Lisp value. Any new feature must preserve this invariant — see Data Model and Mapping.
- Bound untrusted input. New reader or writer paths that can grow with input size must be governed by a limit. See Resource Limits and Security.
- Bounded diagnostics. Attacker-influenced strings, paths, and expected values in conditions are truncated and escaped at construction. Keep it that way.
- Portable core, SBCL extras isolated. The only implementation-specific
behavior is the
:timeout-secondssafeguard; everything else is portable Common Lisp. - Zero runtime dependencies, by design. The runtime system depends on
nothing outside the Common Lisp standard; only the test system uses
cl-weave. Before adding a dependency, check that it earns its keep over hand-rolling the few dozen lines it would save — the nerima-lisp org's other packages were surveyed and none fit a pure string/stream JSON codec (e.g.cl-boundary-kitabstracts filesystem/network/clock/process boundaries this library never touches;cl-parser-kitis a generic parser toolkit that would regress the reader's hand-tuned hot path). Don't wrap a dependency in an adapter layer just to use it "properly" — use it directly or not at all.
Building the documentation¶
The documentation is Material for MkDocs. Build it reproducibly and fully offline with Nix:
Or, with a local MkDocs install:
mkdocs serve -f docs/mkdocs.yml # live preview at http://127.0.0.1:8000
mkdocs build -f docs/mkdocs.yml --strict
--strict promotes broken links and pages missing from the navigation to build
failures, so keep the nav: in
docs/mkdocs.yml
in sync with the files under docs/src/. Pushing a documentation change to
main publishes it to GitHub Pages via the
Publish documentation
workflow.
Reporting issues¶
Use the issue tracker for bugs and questions. For a parse or serialization bug, include the exact input, the options passed, the expected result, and what you observed — the error path and coordinates from the signalled condition are especially helpful.