blazingly-json 0.1.0

A focused, Tokio-free JSON engine for small protocol and API payloads
Documentation
# blazingly-json

[![CI](https://github.com/sergii-ziborov/blazingly-json/actions/workflows/ci.yml/badge.svg)](https://github.com/sergii-ziborov/blazingly-json/actions/workflows/ci.yml)

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:

```toml
[dependencies]
blazingly-json = { git = "https://github.com/sergii-ziborov/blazingly-json" }
serde = { version = "1", features = ["derive"] }
```

After publication, the Git dependency becomes:

```toml
blazingly-json = "0.1"
```

### Serde-compatible typed JSON

```rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
struct Request<'a> {
    method: &'a str,
    limit: u64,
}

let request: Request<'_> =
    blazingly_json::from_str(r#"{"method":"search","limit":20}"#)?;
let encoded = blazingly_json::to_string(&request)?;
# Ok::<(), blazingly_json::Error>(())
```

### 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.

```rust
use blazingly_json::{RawJson, Value};
use serde::Deserialize;

#[derive(Deserialize)]
struct Call<'a> {
    method: &'a str,
    #[serde(borrow)]
    arguments: RawJson<'a>,
}

let call: Call<'_> = blazingly_json::from_str(
    r#"{"method":"query_graph","arguments":{"limit":20}}"#,
)?;
let arguments = call.arguments.deserialize::<Value>()?;
# Ok::<(), blazingly_json::Error>(())
```

### Drop-in-style `RawValue`

The type is available both at `blazingly_json::value::RawValue` and at the
crate root.

```rust
use blazingly_json::value::{to_raw_value, RawValue};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Input<'a> {
    #[serde(borrow)]
    payload: &'a RawValue,
}

#[derive(Serialize)]
struct Output<'a> {
    payload: &'a RawValue,
}

let input: Input<'_> =
    blazingly_json::from_str(r#"{"payload": { "limit": 20 }}"#)?;
assert_eq!(input.payload.get(), r#"{ "limit": 20 }"#);

let output = blazingly_json::to_string(&Output {
    payload: input.payload,
})?;
assert_eq!(output, r#"{"payload":{ "limit": 20 }}"#);

let owned = to_raw_value(&[1, 2, 3])?;
assert_eq!(owned.get(), "[1,2,3]");
# Ok::<(), blazingly_json::Error>(())
```

### Order-independent protocol cursor

`JsonCursor` visits only routing fields, skips unknown values safely, and validates
the complete document.

```rust
use blazingly_json::JsonCursor;

let mut method = None;
let mut cursor = JsonCursor::from_str(r#"{"jsonrpc":"2.0","method":"ping"}"#);
cursor.object(|request| {
    while let Some(field) = request.next_field()? {
        match field.name() {
            "method" => method = Some(field.string()?),
            _ => field.skip()?,
        }
    }
    Ok(())
})?;
cursor.end()?;
assert_eq!(method.as_deref(), Some("ping"));
# Ok::<(), blazingly_json::Error>(())
```

### Schema-aware canonical path

Use this only as a recognizer with a mandatory fallback. A successful
recognizer must consume the complete input.

```rust
use blazingly_json::CanonicalScanner;

let input = r#"{"method":"search","limit":20}"#;
let mut scanner = CanonicalScanner::new(input);
let recognized = (|| {
    scanner.literal(r#"{"method":"#)?;
    let method = scanner.plain_string()?;
    scanner.literal(r#","limit":"#)?;
    let limit = scanner.unsigned()?;
    scanner.literal("}")?;
    scanner.is_finished().then_some((method, limit))
})();

if let Some((method, limit)) = recognized {
    assert_eq!((method, limit), ("search", 20));
} else {
    // Parse with JsonCursor or from_str: the input may still be valid JSON.
}
```

## Supported surface

- `Value`, `Map<String, Value>`, `Number`, and `json!`;
- `from_str`, `from_slice`, `from_value`;
- `to_string`, pretty/string/vector/writer variants, and `to_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 `RawValue` and `to_raw_value`;
- routing-oriented `JsonCursor`;
- exact-layout `CanonicalScanner` and `CanonicalBytesScanner`;
- 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_json` implementation details other than the established
  `RawValue` Serde 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
  borrowed `RawValue`;
- `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]docs/benchmarks.md;
- [consumer-derived compatibility contract]docs/consumer-contract.md;
- [consumer compile/test probes]docs/consumer-validation.md;
- [competitor and MCP runtime analysis]docs/competitors.md;
- [mcport integration spike]docs/mcport-integration.md.

## Verification

```text
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