reliakit-json
A strict, bounded, and deterministic JSON library for Rust.
reliakit-json is built for systems that process untrusted JSON or need
predictable output — API payloads, protocol messages, config, audit logs,
and signed or hashed documents. It parses a strict subset of RFC 8259, rejects
duplicate object keys, enforces explicit resource limits, preserves number
precision, reports errors with location and path, and serializes
deterministically. It has no external dependencies, forbids unsafe code, and
runs on no_std (with alloc).
Its scope is deliberately narrow: no derive macros, no schema validation, no JSON5, no comments, no trailing commas, no lenient parsing, no SIMD. The goal is predictable behavior on hostile input, not maximum throughput or convenience.
What it guarantees
- Strict parsing. A conservative, I-JSON-oriented subset of RFC 8259.
- Duplicate keys are rejected, not silently resolved — and detection happens
after escape decoding, so
"role"and"role"collide. - Explicit resource limits are part of the API. Untrusted parsing cannot run away on input size, nesting depth, string/number length, item counts, or total nodes.
- Numbers keep their exact text. No silent rounding; conversions to
i64/u64/f64are explicit and fallible.NaN/Infinityare never accepted. - Actionable errors with a stable kind, byte offset, line, column, and the
JSON path (
$.users[4].email) being processed. - Deterministic output. The compact writer preserves member order and exact number text; the same value always serializes to the same bytes.
no_std + alloc, zero dependencies,#![forbid(unsafe_code)].
Rejected by default
Invalid UTF-8, a leading byte-order mark, comments, trailing commas, trailing
data, unescaped control characters, invalid escapes, malformed \uXXXX,
unpaired UTF-16 surrogates, duplicate keys, NaN/Infinity, leading +,
leading zeros, malformed numbers, and anything exceeding the configured limits.
Installation
[]
= "0.2"
This crate is no_std-compatible (default-features = false); the default
std feature only adds std::error::Error implementations. It always requires
alloc.
Usage
use ;
let value = parse_str.unwrap;
let object = value.as_object.unwrap;
assert_eq!;
assert_eq!;
// Deterministic, member-order-preserving output.
assert_eq!;
Parse untrusted input under explicit limits:
use ;
let limits = conservative.with_max_depth;
match parse_with_limits
parse and parse_str apply JsonLimits::new (a conservative default)
automatically — there is no implicitly unlimited entry point.
API
| Item | Purpose |
|---|---|
parse(&[u8]) / parse_str(&str) |
Parse with default limits. |
parse_with_limits(&[u8], JsonLimits) |
Parse with an explicit limit profile. |
JsonValue |
Owned value: Null, Bool, Number, String, Array, Object. |
JsonNumber |
Precision-preserving number; to_i64 / to_u64 / to_f64 / try_from_f64. |
JsonObject |
Unique-key, insertion-ordered map (get, insert, iter). |
JsonLimits |
new / conservative / permissive profiles + with_* builders. |
JsonError / JsonErrorKind / JsonLimitKind / JsonPath |
Located, classified errors. |
to_compact_string / to_compact_vec |
Deterministic compact serialization. |
Numbers
JSON numbers are kept as their exact source text and never auto-converted to
f64. Equality is structural — 1.0, 1, and 1e0 are distinct values;
compare numerically by converting first. Conversions report whether they failed
on range, integer-ness, or finiteness.
Limits
JsonLimits bounds logical decoded data (counts and byte lengths), not exact
allocator memory. Profiles: conservative() (small, low-trust payloads),
new() (the default), and permissive() (larger trusted documents) — all
explicit and finite. Tune individual fields with the with_* builders.
When to use it
- Parsing untrusted JSON where predictable failure matters more than speed.
- Producing stable, deterministic JSON for hashing, signing, or content addressing.
- Embedded or
no_stdservices that still need a careful JSON reader/writer.
When not to use it
- You want automatic struct (de)serialization driven by derive macros.
- You need the fastest possible parsing throughput above all else.
- You need JSON5, comments, or lenient parsing — this crate rejects them by design.
Canonical output (RFC 8785 / JCS)
Behind the off-by-default canonical feature, to_canonical_string and
to_canonical_vec produce RFC 8785 (JSON Canonicalization Scheme) output: a
single deterministic byte sequence suitable for hashing or signing. Object keys
are sorted by UTF-16 code units, whitespace is removed, strings use minimal
escaping, and numbers use the shortest ECMAScript Number.toString form.
[]
= { = "0.2", = ["canonical"] }
use ;
let value = parse_str.unwrap;
assert_eq!;
Numbers are treated as IEEE-754 doubles, as the scheme requires: a value with
more precision than an f64 (e.g. an integer above 2^53) is canonicalized as
the nearest double, and a magnitude that overflows to infinity returns an error.
Number formatting is checked against the RFC 8785 examples and round-trips every
canonical number back to the same f64 across a large randomized sample; key
ordering, escaping, and idempotence are covered by tests.
Feature flags
| Feature | Default | Effect |
|---|---|---|
std |
yes | Implements std::error::Error for the error types. |
canonical |
no | Enables RFC 8785 canonical serialization. |
Disable default features for no_std; the crate always requires alloc.
Safety
#![forbid(unsafe_code)]. Parsing uses depth-bounded descent and saturating
arithmetic; there is no known input that causes a panic or unbounded work.
Minimum Supported Rust Version
Rust 1.85 and newer. No nightly features are used.
License
Licensed under the MIT License. See LICENSE.