spate-json 0.1.0

JSON deserialization for the Spate framework: single-document, NDJSON, and top-level-array framings decoded into serde types or dynamically-typed values. Applications should depend on the `spate` facade crate with the `json` feature.
Documentation
# spate-json

JSON deserialization for the [Spate](https://github.com/spate-etl/spate)
framework. Decodes JSON payloads from a source into your own serde types or
dynamically-typed values.

Applications should depend on the **`spate`** facade crate with the **`json`**
feature rather than on this crate directly.

## Framings

One payload maps to 0..N records, chosen by `framing`:

- `single` — the whole payload is one JSON document → one record (the
  Kafka-message default). Empty/whitespace-only payloads are tombstones.
- `ndjson` — newline-delimited JSON (JSON Lines): one value per `\n`-separated
  line → one record per line, with **per-line** error isolation.
- `array` — a top-level JSON array → one record per element (decoded in one
  pass; a malformed array is handled atomically).

Those frame a payload already in memory (a Kafka message). For a **streaming**
source that never holds a whole object in RAM (`spate-s3`, an HTTP body), the
crate also owns the byte-stream framer: `NdjsonFramer` is a chunk-fed, bounded
`RecordFramer` the source runs to cut the stream into records — wire it in with
`S3Source::with_framer(|| Box::new(NdjsonFramer::new(max)))`, then the
deserializer decodes each line in `single` mode.

## Configuration

```yaml
deserializer:
  json:
    framing: single              # single | ndjson | array
    on_error: skip               # skip | fail
    reject_duplicate_keys: false
```

```rust
let deserializer = JsonDeserializerBuilder::from_component(deser_section)?
    .with_metrics(ctx.pipeline, "main")
    .build_serde::<Order>();
```

## Errors and metrics

A document that does not parse (or match the target type) is a record-level
error. `on_error: skip` drops it, counts it in
`spate_json_deser_records_dropped_total{reason}`, and continues; `on_error: fail`
stops the batch, which replays under at-least-once. The framework's generic
`spate_deser_*` stage metrics wrap every decoder in addition.

## Fidelity features

`reject_duplicate_keys` (config) turns silent last-value-wins on duplicate
object keys into an error. Opt-in Cargo features pass straight through to
`serde_json`: `float-roundtrip`, `arbitrary-precision` (crate-wide; interacts
with flatten/untagged/RawValue), and `raw-value`.

## Backends

Decoding uses `serde_json` (stable 1.x), behind an internal seam so a SIMD
backend can be added later without an API change. `from_reader` is never used —
decoding always operates on the in-memory payload slice.