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.
Summary
Functions
Decodes a JSON-decoded term into a predicator value.
Encodes a predicator value into its ADR-0005 JSON-native term.
Functions
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}
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}