Idea: canonical / deterministic serialization (RFC 8785 JCS)
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 50.6k
- Forks
- 7.5k
- Avg merge
- 4d 17h
- Merged PRs (30d)
- 58
Description
Status: brainstorming, not a plan
This is an idea to move forward on, not a sanctioned TODO or a commitment. It is a hypothesis to investigate, measure, and discuss. Treat this as a starting point for discussion.
Motivation
Signing, hashing, content-addressing, reproducible builds, and merkle/blockchain use cases need two serializations of equal documents to be byte-identical. There is currently no way to get that without post-processing the output. RFC 8785 (JSON Canonicalization Scheme, JCS) is the relevant standard.
Of the net-new ideas, this one has the best effort-to-impact ratio: it's a self-contained serializer variant with no core/ABI changes.
What JCS mandates, and where it rubs against dump()
-
Object keys sorted by UTF-16 code unit. The default
object_tisstd::map, sorted bystd::string::operator<— i.e. UTF-8 byte order. For BMP characters that matches UTF-16 order, but for characters ≥ U+10000 it does not (UTF-8 byte order sorts by code point; UTF-16 sorts surrogate pairs, so U+E000–U+FFFF sort after supplementary characters). Canonical mode therefore cannot rely on the container's ordering and must sort keys itself with a UTF-16-code-unit comparator.ordered_json(insertion order) needs the same treatment. -
Number formatting = ECMAScript
Number::toString. Our dtoa already produces shortest round-trip output (≈90% there), but JCS pins the exact rules: plain notation for1e-6 ≤ |x| < 1e21, exponential outside, specifice+/e-formatting, integers up to 2^53, no trailing.0. This needs an audit against the spec's reference, not an assumption of compatibility. -
Only finite numbers; UTF-8 output; minimal escaping.
NaN/Infmust be a hard error in canonical mode (we currently emitnull), and escaping must be the minimal JSON set.
Sketch
A serializer variant, not a tree transform — e.g. dump_canonical() or dump(..., serialization_policy::rfc8785). During traversal it sorts each object's keys into a temporary std::vector<const_iterator> with the UTF-16 comparator and swaps in the ECMAScript number formatter. It never mutates the document, so there's no ABI impact.
std::string canonical = j.dump_canonical(); // illustrative name
Implementation notes
- Lives entirely in the serializer (
include/nlohmann/detail/output/serializer.hpp); no changes tobasic_jsonstorage. - UTF-16-code-unit key comparator: compare by transcoding UTF-8 → UTF-16 code units (or an equivalent code-unit-order comparison directly on UTF-8) — this is the easy-to-get-wrong part that passes casual tests but fails above the BMP.
- ECMAScript number formatting: verify the exponent thresholds and integer handling against the RFC 8785 reference vectors; reject non-finite values with a clear error.
- Ship with the official JCS test vectors (RFC 8785 appendix / reference implementation test suite).
Open questions
- API shape: a dedicated method vs. a flag/policy on
dump(); how it composes withindent(canonical implies no insignificant whitespace, so indent must be ignored or rejected). - Where to draw the line vs. full RFC 8785 (e.g. Unicode normalization is explicitly not required by JCS — confirm we don't over-implement).
- Behavior for
binaryvalues (not representable in JCS) — error.
Dependencies / interactions
- Deliberately overrides the lossless-number idea: raw number tokens must be re-canonicalized (parse → ECMAScript format), not echoed.
- Independent of the borrowed-view and arena ideas.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with include/nlohmann/detail/output/serializer.hpp and RFC 8785, then audit the existing dtoa behavior against the RFC reference vectors. Investigate UTF-16 key ordering, ECMAScript number formatting, non-finite and binary-value errors, and the API and indent behavior. Done means an agreed serializer design validated by the official JCS test vectors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100