Qubit JSON
Resource-aware JSON infrastructure for Rust services, configuration readers,
and data pipelines. It complements serde_json by admitting JSON under
caller-chosen limits before untrusted input consumes unbounded parsing,
materialization, or output resources, while preserving Serde's data model.
Use JsonDecoder when the input must already be strict JSON, or
NormalizingJsonDecoder when the boundary explicitly permits controlled
cleanup of JSON embedded in external text.
Installation
Current 0.9 release:
[]
= "0.9"
= { = "0.5", = ["json"] }
= "1.0"
Local checkout:
[]
= { = "0.9", = "../rs-json" }
= { = "0.5", = ["json"] }
= "1.0"
Add serde = { version = "1.0", features = ["derive"] } when decoding into
derived application types.
Quick start: admit an HTTP request body
This example accepts one request body containing a full-range u64 identifier,
then demonstrates that an oversized body is rejected before a decoded value is
committed:
use JsonDecodeLimits;
use JsonResource;
use JsonDecodeError;
use JsonDecodeErrorKind;
use JsonDecoder;
Unlike calling serde_json::from_slice alone, this boundary makes resource
admission explicit and exposes a stable error category through kind(). Apply
schema validation, authorization, and domain rules after a value is admitted.
Choose the decoding boundary
Choose the entry point from the input contract, not from whether the payload happens to parse:
| Input contract | API |
|---|---|
| Input must already be complete, strict JSON | JsonDecoder |
| Specific presentation artifacts are allowed before strict decoding | NormalizingJsonDecoder with an explicit policy |
| Normalized text must be inspected, decoded repeatedly, or borrowed by the result | NormalizingJsonDecoder::prepare_str / prepare_utf8, followed by NormalizedJsonDocument decoding methods |
The default normalization policy can trim surrounding whitespace, remove one BOM, unwrap one outer JSON Markdown fence, and escape raw ASCII control characters inside strings. It is controlled normalization, not another JSON dialect: comments, trailing commas, unquoted keys, and missing syntax remain errors.
Why this project exists
Valid JSON can still be too large, too deep, or too expensive to materialize.
qubit-json keeps JSON syntax and Serde compatibility while letting callers
bound raw and normalized input, nesting, nodes, collection sizes, keys, strings,
numbers, payload, and encoded output. It also makes cumulative accounting and
commit boundaries explicit through qubit-budget sessions and transactions.
What it provides
| Domain | Use it for | Boundary |
|---|---|---|
decode |
Strict JSON admission or explicitly configured text normalization | Normalization applies only configured transformations; it never invents missing JSON syntax |
encode |
Budgeted strict JSON output | Value accounting commits after complete serialization; an I/O failure can still leave bytes in an external writer |
value |
Building budgeted serde_json::Value trees from Serde events |
A seed cannot inspect original number text or enforce text-level range rules |
value::traverse |
Iterative reads or in-place mutations of materialized values | Mutation is incremental; visitor and output-budget failures do not roll back prior changes |
qubit-budget owns limits, resource identities, budgets, and sessions.
qubit-json owns JSON normalization, lexical validation, text codecs, value
construction, and traversal.
Explicit boundaries
- Strict admission validates JSON syntax and the documented numeric range; it
does not require object keys to be unique. Choose a target such as
DuplicateKeyRejectingJsonValuewhen uniqueness is part of the contract. - Negative integers fit
i64, non-negative integers fitu64, and fractional or exponential values must be finitef64. Use strings or domain types for wider integers or exact decimals that must avoid binary rounding. - Set finite limits at every untrusted boundary. Use
unlimited()only for trusted input or data already admitted by another layer; an outer output bound cannot replace input and structural admission before parsing. - The strict and normalizing decoders consume complete
&stror&[u8]inputs. Their input-byte limit admits the supplied slice; it does not cap memory already allocated by an HTTP body aggregator or another transport layer. Apply a bounded read or body-aggregation limit before decoding. - Diagnostics are redacted by default. Configure a strict decoder with
JsonDecoder::with_diagnostic_policy, or a normalizing decoder through its normalization policy. Redacted errors retain only stable classifications and source coordinates: they never retain an offending byte, token/key/value text, or parser/Serde source. EnableDiagnosticPolicy::Detailedonly where input-derived sources are safe to retain and log.
Learn more
- English user guide · 中文用户手册
- JSON number contract · JSON 数字契约
- Design documents · 设计文档
- Benchmark baseline · 基准测试基线
- Migration from 0.3 to 0.8 · 从 0.3 迁移到 0.8
- Changelog · 中文变更记录
- Released API documentation; for the current
branch API, run
cargo doc --all-features --open
Testing
# Run tests with the default feature set
# Run tests with all declared features
# Project CI checks
# Check code coverage
License
Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
Contributing
Contributions are welcome. Please follow the Rust API guidelines, keep public
API documentation and tests current, and run ./align-ci.sh to format code and
./ci-check.sh to satisfy CI requirements before submitting a pull request.
Author
Haixing Hu - Qubit Co. Ltd.
Repository: https://github.com/qubit-ltd/rs-json