blazingly-json
Focused, safe, Tokio-free JSON for MCP, JSON-RPC, API, config, snapshot, and JSONL workloads.
blazingly-json is not an incomplete reimplementation of every
serde_json feature. Its supported surface was derived from real call sites
in mcport, Weavatrix Rust, RadioChron, Blazingly, and BlazingAPI, then tested
differentially against serde_json.
The general compatible parser is only modestly faster. The large gain comes from changing protocol architecture: borrow the envelope, avoid a mutable DOM, serialize responses directly, and use an exact schema-aware recognizer where the producer emits a canonical layout.
Current status: 0.1.0 release candidate. The crate passes differential/property tests, Rust 1.78, strict Clippy, rustdoc, packaging, Linux CI, and Windows CI. No production consumer has been switched yet.
Performance summary
Local Windows measurements on an Intel Core Ultra 7 255U. Paired harnesses alternate engine order and report medians. These are workload measurements, not universal parser claims.
Canonical typed MCP call
A generated compact tools/call recognizer borrows three strings, parses a
u64 and a boolean, verifies complete consumption, and falls back to the
strict order-independent parser on any mismatch.
| Path | Median range | Speedup over typed serde_json |
|---|---|---|
CanonicalBytesScanner over ASCII canonical bytes |
45.94-50.47 ns | 7.35-7.62x |
CanonicalScanner over prevalidated &str |
47.63-51.81 ns | 7.03-7.43x |
strict order-independent JsonCursor |
261.82-284.37 ns | 1.31-1.35x |
typed serde_json derive |
342.85-384.78 ns | 1.00x |
Both canonical paths allocate zero bytes. The byte scanner avoids a complete
UTF-8 pre-pass: it matches structural ASCII directly and validates captured
ASCII strings while scanning. Across three paired runs it is slightly faster
than the already-prevalidated &str path, so byte input no longer pays the
previous 10-26% conversion penalty.
This path intentionally accepts only its exact schema, field order, compact
separators, and plain strings. The fastest byte method
plain_ascii_string rejects non-ASCII fields. Whitespace, reordered fields,
escaped or non-ASCII strings, and another schema are not treated as errors:
they take the strict JsonCursor fallback.
MCP runtime architecture
Borrowed request routing plus direct response serialization compared with
mcport's original owned-Value + clone implementation:
| Request | Borrowed path | Original mcport | Speedup |
|---|---|---|---|
| ping | 344.93 ns | 1,425.04 ns | 4.13x |
| initialize | 1,014.87 ns | 6,683.85 ns | 6.59x |
| tools/list | 2,524.99 ns | 7,987.72 ns | 3.16x |
| tools/call | 2,103.43 ns | 7,298.68 ns | 3.47x |
Allocation measurements for request dispatch:
| Request | Borrowed path | Original mcport |
|---|---|---|
| ping | 0 allocations / 0 bytes | 8 allocations / 670 bytes |
| tools/call with owned arguments | 5 allocations / 668 bytes | 27 allocations / 2,702 bytes |
| canonical typed tools/call | 0 allocations / 0 bytes | 27 allocations / 2,702 bytes |
RawValue
blazingly_json::value::RawValue implements the common
serde_json::value::RawValue contract: borrowed and boxed deserialization,
verbatim serialization, from_string, from_string_unchecked, get,
into_string, to_raw_value, constants, cloning, and direct deserialization
from &RawValue.
Three paired local runs after warm-up produced these ranges:
| Workload | Speedup over serde_json::RawValue |
|---|---|
| small MCP borrowed parse | 1.22-1.59x |
| small MCP boxed parse | 1.21-1.59x |
small MCP from_string |
1.34-1.54x |
small MCP verbatim to_vec |
1.69-2.36x |
| 1,000,000-number borrowed parse, 6.57 MiB | 1.42-1.79x |
| 1,000,000-number boxed parse | 1.19-1.55x |
| compact protocol-record array borrowed parse | 1.62-2.07x |
| large preallocated writer output | approximately 1.00x |
Large output is memory-bandwidth-bound and is reported as parity, not as a
multiple-times win. For a complete raw document, RawValue::to_vec and
RawValue::write_to bypass the generic Serde marker path. Embedded raw values
remain compatible with ordinary to_vec, to_writer, and pretty output.
Allocation measurements:
| Operation | blazingly-json | serde_json |
|---|---|---|
| small borrowed parse | 0 | 1 allocation / 8 bytes |
| small boxed parse | 1 allocation / 154 bytes | 2 allocations / 162 bytes |
from_string with reusable capacity |
0 | 1 allocation / 8 bytes |
| 1,000,000-number borrowed parse | 0 | 0 |
same payload as owned Vec<u64> in serde_json |
- | 1 allocation + 18 reallocations / 8 MiB live |
The raw parser first tries strict compact numeric-array and flat protocol-record recognizers. They must consume a complete value; whitespace, escaping, nesting, or any shape mismatch takes the fully validating general fallback.
Compatible API and large payloads
The compatible owned/typed path does not reach a multiple-times speedup:
| Workload | blazingly-json | serde_json | Difference |
|---|---|---|---|
MCP mutable Value parse |
2.195 us | 2.313 us | +5.37% |
| MCP typed parse | 1.391 us | 1.423 us | +2.33% |
| MCP typed encode | 420.75 ns | 427.20 ns | +1.53% |
| Weavatrix-like graph parse | 77.72 us | 80.00 us | +2.92% |
| Weavatrix-like graph encode | 22.13 us | 24.70 us | +11.64% |
| Cargo artifact JSONL parse | 1.650 us | 1.850 us | +12.13% |
1,000,000 u64 values |
254.9 MiB/s | 249.8 MiB/s | +2.01% |
| 1,000,000 typed records, 80.98 MiB | 150.1 MiB/s | 147.6 MiB/s | +1.70% |
1,000,000 varied MCP Value parses |
69.7 MiB/s | 68.0 MiB/s | +2.48% |
Parsing one million owned records makes exactly 2,000,001 allocations and
retains 100.27 MiB with either engine: the owned strings and vectors dominate,
not the parser. Minimal stripped Windows executables are also effectively tied:
990,208 bytes for blazingly-json and 989,184 bytes for serde_json.
The conclusion is deliberate: replacing serde_json with another compatible
owned DOM cannot honestly promise 2-3x less memory or 2-3x more speed. The
canonical and borrowed APIs exist to remove that ownership work.
Usage
Until the first crates.io release:
[]
= { = "https://github.com/sergii-ziborov/blazingly-json" }
= { = "1", = ["derive"] }
After publication, the Git dependency becomes:
= "0.1"
Serde-compatible typed JSON
use ;
let request: =
from_str?;
let encoded = to_string?;
# Ok::
Zero-copy envelope
RawJson validates a nested value and borrows its exact input without building
a DOM. The payload is materialized only if a selected handler needs it.
use ;
use Deserialize;
let call: = from_str?;
let arguments = call.arguments.?;
# Ok::
Drop-in-style RawValue
The type is available both at blazingly_json::value::RawValue and at the
crate root.
use ;
use ;
let input: =
from_str?;
assert_eq!;
let output = to_string?;
assert_eq!;
let owned = to_raw_value?;
assert_eq!;
# Ok::
Order-independent protocol cursor
JsonCursor visits only routing fields, skips unknown values safely, and validates
the complete document.
use JsonCursor;
let mut method = None;
let mut cursor = from_str;
cursor.object?;
cursor.end?;
assert_eq!;
# Ok::
Schema-aware canonical path
Use this only as a recognizer with a mandatory fallback. A successful recognizer must consume the complete input.
use CanonicalScanner;
let input = r#"{"method":"search","limit":20}"#;
let mut scanner = new;
let recognized = ;
if let Some = recognized else
Supported surface
Value,Map<String, Value>,Number, andjson!;from_str,from_slice,from_value;to_string, pretty/string/vector/writer variants, andto_value;- Serde structs, maps, sequences, options, enums, newtypes, and bytes;
- string, boolean, signed, unsigned, float, null, array, and object values;
- access, mutation, indexing, JSON Pointer, and
take; - validated borrowed
RawJson; - drop-in-style borrowed/boxed
RawValueandto_raw_value; - routing-oriented
JsonCursor; - exact-layout
CanonicalScannerandCanonicalBytesScanner; - strict RFC 8259 parsing with recursion limits and source locations.
Deliberate 0.1 exclusions:
- arbitrary-precision numbers;
- JSON5, comments, trailing commas, NaN, and infinities;
preserve_order;- synchronous
Read-based incremental parsing; - private
serde_jsonimplementation details other than the establishedRawValueSerde token used for serializer interoperability.
Dependencies and safety
The runtime dependencies are serde, memchr, itoa, lexical-core, and
zmij. serde_json is a development-only oracle for differential tests and
benchmarks. Tokio, Hyper, and Axum are absent.
unsafe_code is denied crate-wide. The sole exception is raw_value.rs,
which contains three documented repr(transparent) DST conversions between
str and RawValue. No parser, scanner, serializer, or other module may use
unsafe code. The optional from_string_unchecked API is itself unsafe and
states the same validity invariant as the serde_json API it replaces.
Competitive position
serde_json: broad compatibility, maturity, ecosystem, and an excellent borrowedRawValue;sonic-rs: SIMD parsing and lazy field access;simd-json: SIMD tape plus borrowed and owned DOMs, with mutable-input and unsafe-code trade-offs;serde_json_borrow: borrowed DOM and reduced string allocation;jiter: iterator/schema-oriented parsing plus Serde support.
blazingly-json does not claim to beat every engine on every document. Its
target is portable, low-MSRV protocol performance, a tiny auditable unsafe
island for RawValue, and generated schema-aware MCP/API codecs with a strict
fallback.
See:
- benchmark methodology and full results;
- consumer-derived compatibility contract;
- consumer compile/test probes;
- competitor and MCP runtime analysis;
- mcport integration spike.
Verification
cargo fmt --all -- --check
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo +1.78 check --lib
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
cargo package
cargo bench --bench paired_comparison
cargo bench --bench large_payload
cargo bench --bench allocation_comparison
cargo bench --bench mcp_fast_path
cargo bench --bench mcp_allocations
cargo bench --bench mcport_end_to_end
cargo bench --bench raw_value_comparison
cargo bench --bench raw_value_allocations
License
MIT