lifegraph-json 0.1.1

Dependency-light JSON toolkit with owned, borrowed, tape, and compiled-schema fast paths
Documentation

lifegraph-json

Zero-dependency JSON crate in Rust with owned, borrowed, tape, and compiled-schema paths.

Why

lifegraph-json is aimed at workloads where generic JSON trees leave performance on the table:

  • fast parse-and-inspect flows
  • low-allocation parsing
  • repeated lookup on wide objects
  • repeated serialization of known object shapes

It uses 0 runtime dependencies.

Drop-in style surface

lifegraph-json now includes a small compatibility-oriented API modeled after common serde_json usage:

  • Value
  • Number
  • from_str
  • from_slice
  • to_string
  • to_vec
  • json!
  • value["field"] and value[index]
  • as_str, as_bool, as_i64, as_u64, as_f64
  • is_null, is_array, is_object, etc.

It is not fully drop-in compatible with serde_json yet, but simple code ports are now much easier.

Example: familiar Value usage

use lifegraph_json::{from_str, json, to_string, Value};

let value: Value = from_str(r#"{"ok":true,"n":7}"#)?;
assert_eq!(value["ok"].as_bool(), Some(true));
assert_eq!(value["n"].as_i64(), Some(7));

let built = json!({"msg": "hello", "items": [1, 2, null]});
assert_eq!(built["msg"].as_str(), Some("hello"));

let encoded = to_string(&built)?;
# Ok::<(), Box<dyn std::error::Error>>(())

Example: tape parsing with compiled lookup keys

use lifegraph_json::{parse_json_tape, CompiledTapeKeys, TapeTokenKind};

let input = r#"{"name":"hello","flag":true}"#;
let tape = parse_json_tape(input)?;
let root = tape.root(input).unwrap();
let index = root.build_object_index().unwrap();
let indexed = root.with_index(&index);
let keys = CompiledTapeKeys::new(&["name", "flag"]);
let kinds = indexed
    .get_compiled_many(&keys)
    .map(|value| value.unwrap().kind())
    .collect::<Vec<_>>();

assert_eq!(kinds, vec![TapeTokenKind::String, TapeTokenKind::Bool]);
# Ok::<(), lifegraph_json::JsonParseError>(())

Example: compiled row serialization

use lifegraph_json::{CompiledRowSchema, JsonValue};

let schema = CompiledRowSchema::new(&["id", "name"]);
let row1 = [JsonValue::from(1u64), JsonValue::from("a")];
let row2 = [JsonValue::from(2u64), JsonValue::from("b")];

let json = schema.to_json_string([row1.iter(), row2.iter()])?;
assert_eq!(json, r#"[{"id":1,"name":"a"},{"id":2,"name":"b"}]"#);
# Ok::<(), lifegraph_json::JsonError>(())

Current performance direction

On local release-mode comparisons against serde_json, the strongest wins so far have been in specialized paths such as:

  • tape parsing on medium/report-like payloads
  • deep structural parses
  • wide-object repeated lookup with indexed compiled keys

Best observed outliers so far include roughly:

  • up to ~6x faster on deep structural parses
  • ~4x faster on several tape parse / parse+lookup workloads
  • ~3x faster on indexed repeated lookup over wide objects

This crate is best viewed as a performance-oriented JSON toolkit for specific workloads, with a growing compatibility layer for easier adoption.