# `StatifierUI.Value`
[🔗](https://github.com/riddler/statifier-ui/blob/v0.11.0/lib/statifier_ui/value.ex#L1)

Codec for ADR-0005's JSON encoding of the value domain predicator's
`Predicator.Types` module defines (its `value/0` type).

`decode/1` reads a JSON-decoded term (as produced by the stdlib `JSON`
module) and recovers the predicator value it encodes, resolving the
`$`-prefixed one-key tagged shapes ADR-0005 reserves: `$undefined`,
`$date`, `$datetime`, `$duration`, plus `$redacted`, which ADR-0012 adds
to the reserved set. `encode/1` is the inverse, present so the codec is
round-trip testable; it is not wired to any writer.

`$redacted` decodes to the dedicated atom `:redacted` (operator ruling of
2026-08-29, recorded on ADR-0012). It is deliberately not `:undefined`:
absence and redaction are different claims, and collapsing them is the
failure ADR-0012 exists to prevent.

JSON-native values (booleans, numbers, strings, `nil`, lists, ordinary
maps) map to themselves in both directions. JSON `null` always decodes to
`nil` - a present, null value - never to the `:undefined` sentinel, which
has its own tagged encoding because absence has no positional spelling
inside a list or map value.

Durations are the one value that does not round-trip identically.
`encode/1` always writes all eight units, filling absent ones with `0`,
which is predicator's own contract rather than a widening of it:
`Predicator.Duration.new/1` fills every unit it was not given, and since
predicator 9.0 the expression evaluator seeds all eight too, so
`Predicator.evaluate("3d")` carries `milliseconds: 0` like the rest
(px-69c). `StatifierUI.Shape.duration?/1` nonetheless recognizes any
non-empty subset of the eight units, and that tolerance is viewer-side -
a statement about the value streams this repository renders but did not
produce, not about what predicator emits. A subset map therefore
re-decodes as canonical rather than identical to the input, and
semantically equal to it.

`encode/1` is closed over predicator's value domain (ADR-0005): the
JSON-native scalars, `Date`, `DateTime`, durations, string- or
atom-keyed maps, and lists of the above. A term outside that domain -
a bare atom other than `nil`, `:undefined`, `:redacted`, `true`, or
`false`, a pid, tuple, reference, port, function, or a struct other than
`Date` or `DateTime` - is rejected with
`{:error, {:unsupported_value, term}}` rather than passed through or
allowed to raise.

# `decode`

```elixir
@spec decode(term()) :: {:ok, term()} | {:error, term()}
```

Decodes a JSON-decoded term into a predicator value.

Recurses through lists and map values. A one-key map whose key starts
with `$` and is not one of the five reserved tags is an error: ADR-0005
reserves the whole one-key `$`-prefixed shape, so an unrecognized tag is a
spec violation on the producer's side rather than a host map to pass
through. A multi-key map containing a `$`-prefixed key is an ordinary host
map.

## Examples

    iex> StatifierUI.Value.decode(1999)
    {:ok, 1999}

    iex> StatifierUI.Value.decode(nil)
    {:ok, nil}

    iex> StatifierUI.Value.decode(%{"$undefined" => true})
    {:ok, :undefined}

    iex> StatifierUI.Value.decode(%{"$redacted" => true})
    {:ok, :redacted}

# `encode`

```elixir
@spec encode(term()) :: {:ok, term()} | {:error, term()}
```

Encodes a predicator value into its ADR-0005 JSON-native term.

The inverse of `decode/1`. Not wired to any writer; present so the codec
is round-trip testable.

A term outside predicator's closed value domain - a bare atom other than
`nil`, `:undefined`, `:redacted`, `true`, or `false`, a pid, tuple,
reference, port,
function, or a struct other than `Date` or `DateTime` - returns
`{:error, {:unsupported_value, term}}`. This function never raises.

## Examples

    iex> StatifierUI.Value.encode(:undefined)
    {:ok, %{"$undefined" => true}}

    iex> StatifierUI.Value.encode(:redacted)
    {:ok, %{"$redacted" => true}}

    iex> StatifierUI.Value.encode(1999)
    {:ok, 1999}

---

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