lifegraph-json
A tiny zero-dependency JSON crate that can beat serde_json by up to 3.78x on the serde-rs/json-benchmark corpus, and by ~6x on some structural parse workloads.
lifegraph-json is a fast JSON value layer for Rust with owned, borrowed, tape, and compiled-schema paths.
Why this exists
serde_json is fantastic infrastructure, but it optimizes for broad ecosystem integration and typed serde workflows.
lifegraph-json optimizes for a different target:
- 0 runtime dependencies by default
- fast parse-and-inspect flows
- low-allocation parsing
- wide-object lookup
- repeated serialization of known object shapes
- a compatibility-focused
Value-style API for easier swapping
If you want a small, fast, hackable JSON layer with aggressive specialized paths, this is the crate.
Important compatibility note
lifegraph-json intentionally does not depend on serde by default.
That means some serde_json functionality is intentionally out of scope today, including typed serde-driven conversions and broader serde ecosystem integration. If you need things like:
from_str::<T>Serialize/Deserializeto_value/from_value- full serde ecosystem compatibility
then you should keep using serde_json.
If you want a fast zero-dependency JSON value layer, use lifegraph-json.
What feels drop-in already
lifegraph-json includes a growing compatibility-oriented API modeled after common serde_json usage:
Value,Number,Mapfrom_str,from_slice,from_readerto_string,to_vec,to_writerto_string_pretty,to_vec_pretty,to_writer_prettyjson!value["field"]andvalue[index]- generic
get,get_mut, plusget_index,get_index_mut pointer,pointer_mut,takeas_str,as_bool,as_i64,as_u64,as_f64is_null,is_array,is_object,len,is_empty,sort_all_objects- nested mutable indexing like
value["a"]["b"] = ... - primitive comparisons like
assert_eq!(value["ok"], true)
In practice this now covers most common Value-centric serde_json code paths.
Quick migration sketch
// before
// use serde_json::{json, Value};
// after
use ;
let value: Value = from_str?;
assert_eq!;
assert_eq!;
let built = json!;
let encoded = to_string?;
# Ok::
Normalized benchmark snapshot
Benchmarked locally in release mode against the official serde-rs/json-benchmark data corpus (canada.json, citm_catalog.json, twitter.json, commit 17b13dd).
To normalize out CPU differences, the main takeaway here is the ratio versus serde_json, not the raw MB/s.
Best observed ratios on this machine
- tape parse: up to 3.78x faster
- borrowed parse: up to 2.37x faster
- owned parse: up to 1.35x faster
- DOM stringify: up to 1.12x faster on this corpus
Geometric-mean ratios across the three benchmark files
- owned parse:
lifegraph-json1.17xserde_json - borrowed parse:
lifegraph-json1.55xserde_json - tape parse:
lifegraph-json2.93xserde_json - DOM stringify:
serde_json1.42xlifegraph-json
Per-file snapshot
| Corpus | Owned parse | Borrowed parse | Tape parse | DOM stringify |
|---|---|---|---|---|
canada.json |
serde_json 1.13x |
serde_json 1.11x |
lifegraph-json 2.39x |
serde_json 3.27x |
citm_catalog.json |
lifegraph-json 1.33x |
lifegraph-json 1.76x |
lifegraph-json 2.79x |
lifegraph-json 1.12x |
twitter.json |
lifegraph-json 1.35x |
lifegraph-json 2.37x |
lifegraph-json 3.78x |
lifegraph-json 1.03x |
So the honest story is:
lifegraph-jsonis not faster everywhere- its tape and borrowed paths are where the strongest wins live
- stringify is getting better, but
serde_jsonstill wins overall there - if your workload is parse-heavy or parse-and-inspect heavy,
lifegraph-jsongets very interesting
Performance direction beyond the benchmark corpus
Outside the json-benchmark corpus, local specialized benchmarks have also shown larger outliers on structural-heavy workloads, including roughly:
- ~4x faster on several tape parse / parse+lookup workloads
- ~3x faster on indexed repeated lookup over wide objects
- up to ~6x faster on some deep structural parse cases
This crate is best viewed as a performance-oriented JSON toolkit with a growing serde_json-style compatibility layer.
Reader/writer example
use ;
use Cursor;
let value = from_reader?;
let mut out = Vecnew;
to_writer?;
# Ok::
Tape parsing example
use ;
let input = r#"{"name":"hello","flag":true}"#;
let tape = parse_json_tape?;
let root = tape.root.unwrap;
let index = root.build_object_index.unwrap;
let indexed = root.with_index;
let keys = new;
let kinds = indexed
.get_compiled_many
.map
.;
assert_eq!;
# Ok::
json! macro parity
The macro is much closer to serde_json in practice, including expression-key object entries:
# use json;
let code = 200;
let features = vec!;
let value = json!;
assert_eq!;