Skip to content

fix: reject non-finite JSON contract values - #361

Open
codeforester wants to merge 2 commits into
mainfrom
bug/342-20260918-strict-json-numbers
Open

codeforester wants to merge 2 commits into
mainfrom
bug/342-20260918-strict-json-numbers

Conversation

@codeforester

Copy link
Copy Markdown
Contributor

Summary

  • Serialize JSON and NDJSON with strict RFC-compliant number handling.
  • Serialize each NDJSON record fully before writing, and document the contract.
  • Add regression tests for nested NaN and infinity across public emitters.

Closes #342

Validation

  • uv run --extra dev --extra typer --extra quality python -m pytest tests/test_output.py tests/test_json_contracts.py -q
  • uv run --extra dev --extra typer --extra quality ruff check lib/python/base_cli/output.py lib/python/base_cli/json_contracts.py tests/test_output.py tests/test_json_contracts.py
  • git diff --check

return "true" if value else "false"
if isinstance(value, (Mapping, list, tuple)):
return json.dumps(value, separators=(",", ":"))
return json.dumps(value, separators=(",", ":"), allow_nan=False)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correctness (reproduced): adding allow_nan=False to _cell_value() extends strict-JSON rejection to the CSV/TSV/table rendering path too, and render_records() writes CSV/TSV rows one at a time — so a later record's non-finite value now raises after earlier rows are already flushed to the stream. This reintroduces, for CSV/TSV, exactly the partial-output problem this PR's changelog/docs claim to fix for NDJSON. Pre-diff, the same value just embedded a literal NaN string in the cell and never raised.

redact_json_value(dict(envelope)),
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reuse (root cause of a gap in inspection.py): allow_nan=False was hand-added at six separate call sites across this module and output.py instead of routing through one shared strict-JSON helper. A future emitter added without remembering to pass it by hand can reintroduce silent non-finite leakage — this already happened: render_inspection_json() in lib/python/base_cli/inspection.py was not updated and still uses plain json.dumps() with no allow_nan=False, despite docs/json-contracts.md's new claim that all JSON/NDJSON emitters reject non-finite values. Worth centralizing into one choke point.

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.

bug: reject non-finite numbers in JSON and NDJSON contracts

1 participant