blazingly-json 0.1.2

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

Written after migrating the Blazingly framework off `serde_json`, and after
profiling the framework's hot paths against Axum, Actix Web, bare hyper and
FastAPI. Everything below is either a measured number or a concrete API need,
not a wish list.

## 1. Own the oracle

The framework used to carry `serde_json` as a dev-dependency so its integration
tests could re-parse the bytes it put on the wire with a second, unrelated
implementation. That exists to catch one specific class of bug: an encoder that
emits something malformed and a decoder that accepts it because it shares the
same misunderstanding. A symmetric bug is invisible to a round-trip through one
implementation.

That check cannot move to blazingly-json's own API — an implementation cannot
be its own oracle. It has to move to blazingly-json's *repository*, which is
where the encoder now lives, and where `tests/differential.rs` already keeps
`serde_json` as a dev-dependency.

The framework will drop its `serde_json` dev-dependency once this repository
owns the property properly. Today `tests/differential.rs` is a fixed set of
hand-written cases, which is a smoke test rather than an oracle. To carry the
weight it needs:

- **Property-based round-tripping.** `proptest` is already a dev-dependency.
  Generate arbitrary `Value` trees — deep nesting, all number forms, strings
  with control characters, lone surrogates, ``, very long keys, duplicate
  keys — encode with blazingly-json, and assert `serde_json` parses the result
  to an equal value. Then the reverse direction.
- **The standard conformance corpus.** JSONTestSuite's `y_` / `n_` / `i_` files
  are the accepted external specification of what a parser must accept, must
  reject, and may choose. A parser that has never run against it has an unknown
  boundary. Vendor the corpus and assert the classification.
- **A fuzz target.** `cargo-fuzz` against `from_slice`, with the invariant that
  anything blazingly-json accepts, `serde_json` also accepts, and the parsed
  values are equal. Disagreement is the bug, in either direction.
- **Number edge cases specifically.** `-0.0`, `1e400`, `1e-400`, integers past
  `i64`/`u64`, and the exact round-trip of `f64` values. `lexical-core` and
  `zmij` are strong here, which is exactly why the boundary should be pinned by
  a test rather than assumed.

## 2. What the framework's profile says it needs

An ablation of the framework's listing endpoint — a ~18 KB JSON response with
20 items, each carrying a nested category, author and tag array — measured
server CPU per request with each stage stubbed out:

| variant | CPU/request | delta |
| --- | ---: | ---: |
| full | 219.2 us ||
| no response JSON encoding | 152.8 us | **-30%** |
| no owned-page construction | 141.9 us | -35% |
| no query-model rules | 220.0 us | 0% |
| no extractor at all | 215.6 us | -1.6% |
| no dependency resolution | 219.8 us | 0% |

**Encoding is 30% of per-request CPU.** That is the single largest thing this
crate can move for the framework.

The framework has already removed the 35% by adding `PreparedJson<T>`, a
response type whose operation encodes inside its own scope while borrows into
the store are still alive. It currently encodes through the compatible
`to_vec` path. Your README is explicit that the compatible owned path is only
modestly faster than `serde_json`, and that the real gain is in the borrowed
and canonical APIs — so the 30% is not addressable until `PreparedJson` can
reach those.

**Concretely needed:** a serialization entry point that writes a
`Serialize` value into a caller-supplied `&mut Vec<u8>` without an intermediate
DOM and without re-allocating, ideally with a size hint. The framework already
tracks a per-response-shape size hint and reserves before encoding; today it
then calls `to_writer`, which is the compatible path. If there is a faster
borrowed-oriented writer, expose it and say what it gives up.

## 3. RawJson on the ingest path

The bulk ingestion endpoint takes a 26 KB envelope of 50 items and validates
each one independently, reporting per-item outcomes. It must not fail the batch
on the first bad item, which means it cannot deserialize into
`Vec<CreateArticle>` directly.

`RawJson` is exactly the right shape for this: parse the envelope once, keep
each item as un-decoded bytes, and decode items one at a time so a malformed
item produces a per-item rejection rather than a request-level 422. Confirm
`RawJson` borrows from the input buffer rather than copying, since the whole
point is to avoid materialising 50 sub-documents.

## 4. Number and map behaviour the framework depends on

- `Map` must preserve insertion order for response bodies. The framework's
  OpenAPI document and its contract fingerprints are order-sensitive, and its
  equivalence harness compares parsed JSON across five implementations.
- Integer/float distinction must survive a round trip: an `id` that went out as
  `42` must not come back as `42.0`.
- `to_vec` must not pretty-print or add trailing whitespace; the framework sets
  `Content-Length` from the encoded length.

## 5. What the framework does not need

Not `json!` performance — it is used in OpenAPI and MCP document generation,
which happen once at startup, never on the request path. Optimising it would be
effort spent where nothing is measured.

## Measurement note

Every framework-side number above was taken on a Windows host carrying 15-86%
unrelated CPU load, where a single unchanged binary varied by 3-4x between
adjacent samples. The CPU-per-request figures are stable because they measure
work rather than scheduler luck; the throughput figures are directional. Any
optimisation this repository makes should be validated the same way — paired,
interleaved, and on CPU per operation rather than wall-clock throughput.