Skip to main content

spate_json/
lib.rs

1//! JSON deserialization for the Spate framework.
2//!
3//! Decodes JSON payloads carried by a source (a Kafka message, a file line,
4//! an HTTP body) into either your own serde types ([`JsonSerdeDeserializer`])
5//! or dynamically-typed [`JsonValue`] records ([`JsonValueDeserializer`]).
6//! Three framings map onto the framework's "one payload → 0..N records"
7//! contract:
8//!
9//! - **`single`** — the whole payload is one JSON document → one record (the
10//!   Kafka-message default). An empty or whitespace-only payload is a
11//!   tombstone: zero records, no error.
12//! - **`ndjson`** — newline-delimited JSON (JSON Lines): one JSON value per
13//!   `\n`-separated line → one record per line. Blank lines are skipped.
14//!   This is the framing with **per-line** error isolation.
15//! - **`array`** — a top-level JSON array → one record per element, decoded in
16//!   a single pass. A malformed array is handled atomically by the configured
17//!   [error policy](JsonSettings); use `ndjson` when you need per-record
18//!   isolation.
19//!
20//! Those three frame a payload already resident in memory (a Kafka message).
21//! For a **streaming** source that never holds a whole object in RAM — an
22//! `spate-s3` object, an HTTP body — the connector also owns the byte-stream
23//! framer: [`NdjsonFramer`] is a chunk-fed, bounded
24//! [`RecordFramer`](spate_core::framing::RecordFramer) the source runs to cut the
25//! stream into records, then hands each to the deserializer in `single` mode.
26//! Framing this way is a format concern, so it lives here rather than in the
27//! source.
28//!
29//! # Errors and metrics
30//!
31//! A document that does not parse (or does not match the target type) is a
32//! record-level error. Under `on_error: skip` (the default) it is dropped and
33//! counted in `spate_json_deser_records_dropped_total{reason="malformed"}` and
34//! the decode continues; under `on_error: fail` it surfaces a decode error on
35//! the first bad record, which the chain's deserializer error policy then
36//! either replays (stop the pipeline, at-least-once) or drops as a whole
37//! payload. The connector-owned
38//! `spate_json_deser_*` families are minted from a [`Meter`](spate_core::metrics::Meter)
39//! when the builder is given a metrics scope (see
40//! [`JsonDeserializerBuilder::with_metrics`]); they sit alongside the
41//! framework's generic `spate_deser_*` stage metrics, which wrap every decoder.
42//!
43//! # Fidelity knobs
44//!
45//! `reject_duplicate_keys` (config) turns serde_json's silent last-value-wins
46//! on duplicate object keys into a hard error — a guard against upstream
47//! corruption. The optional Cargo features `float-roundtrip`,
48//! `arbitrary-precision`, and `raw-value` pass straight through to
49//! `serde_json`; `arbitrary-precision` in particular is crate-wide and
50//! interacts with `flatten`/`untagged`/`RawValue`, so enable it deliberately.
51//!
52//! # Backends
53//!
54//! Decoding uses `serde_json` (stable 1.x) by default. The byte-slice → value
55//! step sits behind an internal seam, so the opt-in **`simd`** Cargo feature
56//! swaps in `simd-json` (SIMD-accelerated parsing) with no change to this API —
57//! [`BACKEND_ID`] reports which is compiled. `simd` is a decode speedup over
58//! serde_json on the single-document and array paths (the Kafka-message
59//! default), per a benchmark study (see the framework's deserialization-formats
60//! benchmark page). It is off by default and excluded from the facade's `full`
61//! feature. `simd` is *not* byte-for-byte identical to serde_json on every
62//! input — it rejects integer literals outside the `i64`/`u64` range that
63//! serde_json accepts as `f64`, and does not honor serde_json's
64//! `arbitrary_precision` / `raw_value` / `float_roundtrip` features; see the
65//! JSON connector guide's Backends section. `from_reader` is never used on the
66//! hot path — decoding always operates on the in-memory payload slice.
67
68mod backend;
69mod config;
70mod deser;
71mod framing;
72mod metrics;
73
74pub use backend::BACKEND_ID;
75pub use config::{JsonConfigError, JsonDeserializerBuilder, JsonFraming, JsonSettings, OnError};
76pub use deser::{JsonSerdeDeserializer, JsonValueDeserializer};
77pub use framing::NdjsonFramer;
78
79/// Dynamically-typed JSON record, re-exported for the [`JsonValueDeserializer`]
80/// path. Unlike Avro's value type this is a stable 1.x dependency, so it does
81/// not widen the framework's semver surface.
82pub type JsonValue = serde_json::Value;