# `Predicator.Conformance.Values`
[🔗](https://github.com/riddler/predicator-ex/blob/v9.5.0/lib/predicator/conformance/values.ex#L1)

The tagged-value codec for the conformance corpus (`px-35i.4`).

Plain JSON scalars, arrays, and objects mean themselves - including a bare
JSON `null`, which decodes to predicator's null value with no tag of its own
(px-o9v). `docs/isa.md` section 3's value domain also includes `Date`,
`DateTime`, duration, and `:undefined`, none of which JSON can represent
directly, so each of those is carried as an object with a `$type` key:

    {"$type": "date", "value": "2026-08-06"}
    {"$type": "datetime", "value": "2026-08-06T12:00:00Z"}
    {"$type": "datetime", "value": "2026-08-06T12:00:00.500000Z"}
    {"$type": "duration", "value": {"years":0,"months":0,"weeks":0,"days":3,"hours":0,"minutes":0,"seconds":0}}
    {"$type": "undefined"}

A `duration` tag's `value` carries the normative eight-key map from
`docs/isa.md` section 3 - `years`, `months`, `weeks`, `days`, `hours`,
`minutes`, `seconds`, `milliseconds`, all always present in the value
itself. The JSON compacts it: `milliseconds` is emitted only when non-zero,
and `from_json/1` restores it as `0` when absent, so the two forms decode
identically. The other seven units are always written out.

An object with a literal `"$type"` key that is *not* one of predicator's own
values (i.e. supplied inside an authored case's plain map) would be
ambiguous with the tag namespace, so `to_json/1` rejects it rather than
silently emitting a tag.

# `json`

```elixir
@type json() ::
  nil
  | boolean()
  | number()
  | binary()
  | [json()]
  | %{optional(binary()) =&gt; json()}
```

A JSON-shaped term: what `Predicator.Conformance.JSON` writes and `JSON.decode/1` reads.

# `from_json`

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

Decodes a tagged-JSON term back into a predicator value.

The inverse of `to_json/1` up to canonicalization: `to_json(from_json(to_json(v)))
== to_json(v)` for every value `to_json/1` accepts, and `from_json(to_json(v))
== {:ok, v}` for every value except a `DateTime` whose precision field
disagrees with its own sub-second component, which comes back canonicalized.
Returns `{:error, {:unknown_type, type}}` for an object whose `$type` is not
one of `"date"`, `"datetime"`, `"duration"`, or `"undefined"`.

## Examples

    iex> Predicator.Conformance.Values.from_json(%{"$type" => "undefined"})
    {:ok, :undefined}

    iex> Predicator.Conformance.Values.from_json(%{"$type" => "date", "value" => "2026-08-06"})
    {:ok, ~D[2026-08-06]}

    iex> Predicator.Conformance.Values.from_json(42)
    {:ok, 42}

    iex> Predicator.Conformance.Values.from_json(%{"$type" => "nope"})
    {:error, {:unknown_type, "nope"}}

# `to_json`

```elixir
@spec to_json(term()) :: {:ok, json()} | {:error, {:unencodable, term()}}
```

Encodes a predicator value (`t:Predicator.Types.value/0`, plus plain maps
and lists of the same) into the tagged JSON encoding.

Returns `{:error, {:unencodable, term}}` for anything outside the ISA
value domain, and for a plain map that contains a literal `"$type"` key.

## Examples

    iex> Predicator.Conformance.Values.to_json(:undefined)
    {:ok, %{"$type" => "undefined"}}

    iex> Predicator.Conformance.Values.to_json(~D[2026-08-06])
    {:ok, %{"$type" => "date", "value" => "2026-08-06"}}

    iex> Predicator.Conformance.Values.to_json(~U[2026-08-06T12:00:00Z])
    {:ok, %{"$type" => "datetime", "value" => "2026-08-06T12:00:00Z"}}

    iex> Predicator.Conformance.Values.to_json(~U[2026-08-06T12:00:00.5Z])
    {:ok, %{"$type" => "datetime", "value" => "2026-08-06T12:00:00.500000Z"}}

    iex> Predicator.Conformance.Values.to_json(42)
    {:ok, 42}

    iex> Predicator.Conformance.Values.to_json(%{"$type" => "oops"})
    {:error, {:unencodable, %{"$type" => "oops"}}}

    iex> Predicator.Conformance.Values.to_json({:not, :a, :json, :value})
    {:error, {:unencodable, {:not, :a, :json, :value}}}

---

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