Skip to content

feat(stdlib): make json_encode strict and add json_encode_lossy 🧊 - #199

Merged
timfennis merged 3 commits into
masterfrom
feature/strict-json
Aug 26, 2026
Merged

feat(stdlib): make json_encode strict and add json_encode_lossy 🧊#199
timfennis merged 3 commits into
masterfrom
feature/strict-json

Conversation

@timfennis

@timfennis timfennis commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Context

While working on struct support in #197 (the to_json TODO), we decided JSON conversion should be perfect in both directions: json_encode must be the exact inverse of json_decode and error whenever that's impossible, instead of silently degrading values. Extracted as a precursor PR off master so #197 can rebase on it and add only the struct arm.

Changes

  • json_encode is now strict. It only accepts values for which json_decode(json_encode(v)) == v holds: the unit value (), booleans, ints, big ints, finite floats, strings, lists, string-keyed sets, and maps with string keys. Everything else errors with a hint: rationals, complex numbers, non-finite floats, options (both Some and None), tuples, deques, iterators, heaps, functions, maps with non-string keys or a default value, and cyclic values.
  • New json_encode_lossy accepts the rejected values by degrading them: rationals → floats, complex → strings, Some(x) unwrapped to x, Nonenull, tuples/deques → arrays, heaps → arrays in priority order (deterministic, was arbitrary internal heap order), iterators drained, non-string keys stringified, map defaults dropped, non-finite floats → null. It has no decode counterpart because these conversions cannot be reversed. Functions and cyclic values still error.
  • json_decode fixes: integers beyond i64 decode exactly to big ints via serde_json's arbitrary_precision (previously silently lossy through f64), and numbers that overflow a float (1e999) error.
  • Cycle detection replaces a stack-overflow abort: json_encode([l where l contains l]) used to crash the process, now it's an error.

Notes for reviewers

  • ()null in both directions; None is rejected along with Some so the pairing stays unambiguous (previously both None and () encoded to null).
  • Because a set is runtime-identical to a map with () values, sets are automatically accepted and round-trip exactly as objects with all-null values: %{"a"}{"a":null}.
  • Tuples and deques are rejected by strict encode for the same reason options are: they decode back as a List, so the round trip doesn't hold.
  • Doc comments on the three functions document the full mapping (they feed ndc docs); there's no manual page for JSON yet.

🤖

json_encode now only accepts values that survive an exact round trip
through json_decode and errors on everything else (rationals, complex,
options, tuples, deques, iterators, heaps, sets, unit, non-string map
keys, map defaults, non-finite floats, cyclic values). The new
json_encode_lossy accepts those by degrading them. json_decode now maps
null to None and decodes big integers exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timfennis added a commit that referenced this pull request Aug 26, 2026
Structs are rejected by the strict json_encode (a JSON object decodes
back to a map, not a struct) and encode as objects via json_encode_lossy.
The serde.rs arms landed while resolving the rebase onto #199.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7bb0acecf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_stdlib/src/serde.rs Outdated
…_lossy 🌀

Found by review on #199: a heap pushed into itself overflowed the stack
because only lists, deques and maps were in the cycle guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timfennis added a commit that referenced this pull request Aug 26, 2026
Structs are rejected by the strict json_encode (a JSON object decodes
back to a map, not a struct) and encode as objects via json_encode_lossy.
The serde.rs arms landed while resolving the rebase onto #199.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7122431f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_stdlib/src/serde.rs
() now converts to null and null decodes to (), while None joins Some
as a rejected option value in strict json_encode. As a consequence
string-keyed sets round-trip exactly (as objects with null values), so
they are accepted again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timfennis added a commit that referenced this pull request Aug 26, 2026
Structs are rejected by the strict json_encode (a JSON object decodes
back to a map, not a struct) and encode as objects via json_encode_lossy.
The serde.rs arms landed while resolving the rebase onto #199.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@timfennis
timfennis merged commit f2408b2 into master Aug 26, 2026
1 check passed
@timfennis
timfennis deleted the feature/strict-json branch August 26, 2026 10:01
timfennis added a commit that referenced this pull request Aug 26, 2026
Structs are rejected by the strict json_encode (a JSON object decodes
back to a map, not a struct) and encode as objects via json_encode_lossy.
The serde.rs arms landed while resolving the rebase onto #199.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant