# `PB.JSON`
[🔗](https://github.com/hansihe/pb_ex/blob/v0.2.0/pb/lib/pb/json.ex#L1)

ProtoJSON encoder/decoder for PB.

The headline API works directly with **JSON binary strings**: `encode/4`
returns a JSON-encoded binary, `decode/4` parses a JSON-encoded binary.
Parsing and serialisation are performed with OTP's built-in `:json` module
(OTP 27+), with two ProtoJSON-required customisations baked in:

  * JSON `null` decodes as Elixir `nil` (and Elixir `nil` is encoded as the
    JSON `null` literal) — OTP's default mapping uses the atom `:null`,
    which PB does not.
  * Duplicate JSON object keys are rejected as `kind: :malformed_json`
    errors — OTP's default decoder silently keeps the last occurrence,
    masking ProtoJSON `FieldNameDuplicate` violations.
  * Decode enforces a fixed recursion-depth limit of 100, matching the wire
    codec's guard for recursive input. JSON container nesting, message
    nesting, and `Value`/`Struct`/`ListValue` nesting beyond that return
    `kind: :malformed_json`.

A secondary surface exposes the underlying term-in/term-out pipeline.
`to_term/4` returns a JSON-compatible Elixir term (maps, lists, strings,
numbers, booleans, `nil`) and `from_term/4` accepts one. Use these when
composing with another JSON library or when asserting on the JSON shape
directly.

Like the wire codec, these honor the schema's configured projections: the
decoded/accepted message shape follows the schema's native representation
(adapters, struct/unwrap), so under a projection the value may be a struct, a
tuple, or a bare scalar rather than a plain map.

## Supported

All scalar types, enums (by name or integer), repeated fields, maps, oneofs,
proto2 and proto3 (including proto3 `optional`).

Wrapper messages (`google.protobuf.Int32Value`, `StringValue`, `BoolValue`,
`BytesValue`, etc.) are serialised as their bare scalar value per ProtoJSON.
`google.protobuf.FieldMask` is serialised as a single comma-separated string
with each path converted between snake_case (proto) and lowerCamelCase (JSON).
`google.protobuf.Duration` is serialised as a decimal-seconds string ending
in `"s"`. `google.protobuf.Timestamp` is serialised as an RFC 3339 / ISO 8601
string in UTC ending in `"Z"`. `google.protobuf.Value`, `Struct`, and
`ListValue` round-trip as bare JSON values (null/number/bool/string),
objects, and arrays respectively. `google.protobuf.Any` serialises as an
object with a `@type` URL plus the embedded message — flat-merged when the
embedded message uses the default JSON object form, or under a `value`
field when the embedded message itself has a custom JSON mapping (other
WKTs, including nested `Any`). The `@type` URL may use any host: its last
path segment names the embedded message, as canonical ProtoJSON specifies.
The embedded message honors the schema's projections like any other.

## Options

  * `:emit_defaults` — when `true`, fields without presence that have their
    default value are emitted instead of omitted. Defaults to `false`.
  * `:use_proto_names` — when `true`, the encoder emits field names as
    written in the `.proto` file (snake_case) rather than lowerCamelCase.
    Parsers always accept both spellings. Defaults to `false`.
  * `:enums_as_ints` — when `true`, enums are encoded as their numeric value
    instead of the enum-value name. Defaults to `false`.
  * `:unknown_fields` — `:error` (the default) or `:ignore`. On decode it
    governs JSON keys the message does not define, and enum strings the enum
    does not declare (skipped with `:ignore`, as ProtoJSON's
    `ignore_unknown_fields` does); on encode, map keys the message does not
    define, as for `PB.encode/4`. An error is
    `{:error, %PB.ValueError{kind: :unknown_field}}`.

Encode (`encode/4`, `to_term/4`) accepts `:emit_defaults`, `:use_proto_names`,
`:enums_as_ints` and `:unknown_fields`; decode (`decode/4`, `from_term/4`)
accepts `:unknown_fields`. Like the core codec, the option set is validated
per direction: an unknown key (including a typo such as `emit_default:`), an
option meant for the other direction, or a value the option does not take
returns `{:error, %PB.OptionError{}}` rather than being silently ignored.

# `opts`

```elixir
@type opts() :: [
  emit_defaults: boolean(),
  use_proto_names: boolean(),
  enums_as_ints: boolean(),
  unknown_fields: :error | :ignore
]
```

# `decode`

```elixir
@spec decode(binary(), PB.schema_source(), PB.message_name(), opts()) ::
  {:ok, term()} | {:error, PB.Error.t()}
```

Decodes a JSON binary string into the map shape used by `PB.encode/3`.
Returns `{:ok, map}` or `{:error, error}` (see `PB.Error`).

Two ProtoJSON-required behaviours are baked in: JSON `null` is mapped to
Elixir `nil`, and duplicate JSON object keys are rejected as
`kind: :malformed_json` errors.

# `decode!`

```elixir
@spec decode!(binary(), PB.schema_source(), PB.message_name(), opts()) :: term()
```

Decodes a JSON binary string into the map shape used by `PB.encode/3`,
raising `PB.Error` on failure.

This is the bang variant of `decode/4`.

# `encode`

```elixir
@spec encode(term(), PB.schema_source(), PB.message_name(), opts()) ::
  {:ok, binary()} | {:error, PB.Error.t()}
```

Encodes `data` (the same shape produced by `PB.decode/4`) as a JSON
binary string. Returns `{:ok, binary}` or `{:error, error}` (see `PB.Error`).

# `encode!`

```elixir
@spec encode!(term(), PB.schema_source(), PB.message_name(), opts()) :: binary()
```

Encodes `data` as a JSON binary string, raising `PB.Error` on failure.

This is the bang variant of `encode/4`.

# `encode_iodata`

```elixir
@spec encode_iodata(term(), PB.schema_source(), PB.message_name(), opts()) ::
  {:ok, iodata()} | {:error, PB.Error.t()}
```

Encodes `data` as JSON iodata. Returns `{:ok, iodata}` or
`{:error, error}` (see `PB.Error`).

# `encode_iodata!`

```elixir
@spec encode_iodata!(term(), PB.schema_source(), PB.message_name(), opts()) ::
  iodata()
```

Encodes `data` as JSON iodata, raising `PB.Error` on failure.

This is the bang variant of `encode_iodata/4`.

# `from_term`

```elixir
@spec from_term(term(), PB.schema_source(), PB.message_name(), opts()) ::
  {:ok, term()} | {:error, PB.Error.t()}
```

Decodes a parsed JSON term into the map shape used by `PB.encode/3`.
Returns `{:ok, map}` or `{:error, error}` (see `PB.Error`).

# `from_term!`

```elixir
@spec from_term!(term(), PB.schema_source(), PB.message_name(), opts()) :: term()
```

Decodes a parsed JSON term into the map shape used by `PB.encode/3`,
raising `PB.Error` on failure.

This is the bang variant of `from_term/4`.

# `to_term`

```elixir
@spec to_term(term(), PB.schema_source(), PB.message_name(), opts()) ::
  {:ok, term()} | {:error, PB.Error.t()}
```

Encodes `data` into a JSON-compatible Elixir term (maps, lists, strings,
numbers, booleans, `nil`). Returns `{:ok, term}` or
`{:error, error}` (see `PB.Error`).

Use this when composing with a different JSON library or when asserting on
the JSON shape directly.

# `to_term!`

```elixir
@spec to_term!(term(), PB.schema_source(), PB.message_name(), opts()) :: term()
```

Encodes `data` into a JSON-compatible Elixir term, raising `PB.Error`
on failure.

This is the bang variant of `to_term/4`.

---

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