# `Rupa.Json`
[🔗](https://github.com/zero-one-group/rupa/blob/v0.1.0/lib/rupa/json.ex#L1)

The JSON edge: bytes in through OTP's `:json`, iodata out from the staged program.

The two directions are not the same problem, and only one of them fuses.

**Encoding does.** `Rupa.encode_json/3` walks the decoded value once and emits iodata, so the
wire map `Rupa.encode/3` would have built is never built at all. Wire keys and constant values
are rendered to binaries while the schema is staged, so what is left at run time is escaping
the strings your value actually carries and consing the pieces together.

**Decoding does not**, and the reason is the callback API rather than anything about Rupa.
`:json.decode/3` hands a value's key to `object_push` only after that value is already built,
so when a nested object starts, nothing in scope says which field it belongs to — and a field
is exactly where a schema differs from a parser. The accumulator threads down through
`object_start` and `array_start`, which is enough to direct the root and an array's elements,
and not enough for an object's fields. So `Rupa.decode_json/3` is `:json.decode/3` followed by
`Rupa.decode/3`, which is what the name promises and no more. `bench/json.exs` is the
measurement; a Rupa-owned parser is the way past it, and not in 0.1.0.

Nothing here is public API. `Rupa.decode_json/3` and `Rupa.encode_json/3` are.

## The iodata shape

Both backends build an object or an array as a reversed list of entries, each one carrying its
own leading comma, and the helper that closes the bracket drops the first comma again. That is
how OTP's own encoder does it, and it is why a field costs one cons cell rather than a branch
on whether it is the first.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
