A deterministic JSON writer for the conformance corpus (px-35i.4).
The built-in JSON.encode!/1 does not guarantee map key order, which makes
its output unusable for a byte-compared, checked-in corpus: two semantically
identical maps built by inserting keys in a different order could encode
differently, turning the staleness test red for no reason. encode_canonical/1
fixes that by walking the term itself, sorting object keys by codepoint, and
emitting no incidental whitespace - JSON.encode!/1 is still used underneath
for scalar and string escaping, so the escaping rules stay the standard
library's.
encode_lines/1 is the shipped corpus format: a list of objects, one per
line, so that changing a single case is a one-line diff instead of a
whole-file rewrite.
Summary
Types
A term encodable as JSON: what JSON.decode/1 can produce, plus atom map keys.
Functions
Encodes term as canonical JSON: object keys sorted by codepoint, no
incidental whitespace.
Encodes a list of terms as the shipped corpus format: one canonical-JSON
object per line, each terminated by \n.
Types
@type json_value() :: nil | boolean() | number() | binary() | [json_value()] | %{optional(binary() | atom()) => json_value()}
A term encodable as JSON: what JSON.decode/1 can produce, plus atom map keys.
Functions
@spec encode_canonical(json_value()) :: binary()
Encodes term as canonical JSON: object keys sorted by codepoint, no
incidental whitespace.
Examples
iex> Predicator.Conformance.JSON.encode_canonical(%{"b" => 1, "a" => 2})
~s({"a":2,"b":1})
iex> Predicator.Conformance.JSON.encode_canonical([1, "two", true, nil])
~s([1,"two",true,null])
@spec encode_lines([json_value()]) :: binary()
Encodes a list of terms as the shipped corpus format: one canonical-JSON
object per line, each terminated by \n.
Examples
iex> Predicator.Conformance.JSON.encode_lines([%{"id" => 1}, %{"id" => 2}])
~s({"id":1}\n{"id":2}\n)