Predicator.Conformance.Values (predicator v7.0.0)

Copy Markdown View Source

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 seven-key map from docs/isa.md:100-106 - years, months, weeks, days, hours, minutes, seconds, always present, plus milliseconds only when it is non-zero (predicator's own duration values default milliseconds to 0 the same way the other six units do, so an absent key and a zero key decode identically).

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.

Summary

Types

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

Functions

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

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

Types

json()

@type json() ::
  nil
  | boolean()
  | number()
  | binary()
  | [json()]
  | %{optional(binary()) => json()}

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

Functions

from_json(value)

@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(date)

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

Encodes a predicator value (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}}}