Human-readable JSON parse/decode errors.
Module Json
JSON encoding/decoding.
A reusable parser from a materialized Json value into a typed value.
A Format<a> exposes a single .parse operation that returns .ok value
on success or .err! on mismatch.
type Json = recursive either { .bool Bool, .list List<self>, .null !, .number Float, .object BoxMap.Readonly<String, self>, .string String, }
A materialized JSON value.
Notes:
.numberusesFloat.- Encoding
.number(Float.NaN),.number(Float.Inf), or.number(Float.NegInf)produces JSONnull. - Object keys are normalized through
BoxMap.Readonly, so duplicate source keys and original key order are not preserved.
type Json.ObjectFormat<a> = box choice { .parseObject => [BoxMap.Readonly<String, Json>] Option<a>, }
A reusable parser specialized to JSON objects.
Use Json.Object(...) to lift an ObjectFormat<a> into a full
Format<a>.
dec Json.And : <a: box>[Json.ObjectFormat<a>] <b: box>[Json.ObjectFormat<b>] Json.ObjectFormat<(b) a>
Runs two object formats against the same object and returns both results.
The result shape is (right) left, so
Json.Field("age", Json.Number)->Json.And(Json.Field("name", Json.String))
returns (String) Float.
Both object formats return non-linear values because either side may need to be discarded if the other side fails.
dec Json.Bool : Json.Format<Bool>
Parses a JSON boolean.
dec Json.Decode : [String] Result<Json.Error, Json>
Decodes JSON text into a materialized Json value.
Encodes a Json value into compact JSON text.
Tests two Json values for structural equality.
Object key order does not matter, because objects are normalized through
BoxMap.Readonly.
dec Json.Field : [String] <a>[Json.Format<a>] Json.ObjectFormat<a>
Parses a required object field with the given format.
For example, Json.Field("name", Json.String) accepts an object whose
"name" field is a JSON string.
dec Json.List : <a: box>[Json.Format<a>] Json.Format<List<a>>
Parses a JSON array with the given item format.
The item type must be non-linear because earlier parsed items may need to be discarded if a later item fails to parse.
dec Json.Literal : [Json] Json.Format<!>
Matches exactly one JSON value.
dec Json.Map : <a>[Json.Format<a>] [type b, box [a] b] Json.Format<b>
Applies a pure mapping function to a successful parse result.
Use Json.Map when parsing cannot fail once the underlying format
succeeds. Use Json.Then for fallible post-processing.
dec Json.Null : Json.Format<!>
Matches JSON null.
dec Json.Number : Json.Format<Float>
Parses a JSON number.
dec Json.Object : <a>[Json.ObjectFormat<a>] Json.Format<a>
Parses a JSON object with the given object format.
dec Json.OptionalField : [String] <a>[Json.Format<a>] Json.ObjectFormat<Option<a>>
Parses an optional object field with the given format.
Missing fields produce .ok .err!. Present fields must parse successfully.
dec Json.OrNull : <a>[Json.Format<a>] Json.Format<Option<a>>
Accepts JSON null as .ok .err!, otherwise parses with the given format
and wraps the result in .ok.
dec Json.String : Json.Format<String>
Parses a JSON string.
dec Json.Tagged : [type a, String, List<(Json) <b>(Json.ObjectFormat<b>) box [b] a>] Json.Format<a>
Parses tagged JSON objects by matching one field against literal JSON tags.
The first argument selects the field to inspect. Each branch provides a tag
literal, an object format, and a mapper into the result type. Tags are
compared with Json.Equals, so they may be strings, numbers, booleans, or
null, not just strings.
type Command = either {
.sleep Float,
.say String,
}
def CommandFormat = Json.Tagged(type Command, "operation", *(
(.string "sleep", Json.Field("seconds", Json.Number)) box [seconds] .sleep seconds,
(.string "say", Json.Field("message", Json.String)) box [message] .say message,
))
dec Json.Then : <a>[Json.Format<a>] [type b, box [a] Option<b>] Json.Format<b>
Applies a fallible post-processing step to a successful parse result.
The mapping function returns .ok value to accept the parsed result or
.err! to reject it.
dec Json.Union : [type a, List<<b>(Json.Format<b>) box [b] a>] Json.Format<a>
Tries multiple formats in order and returns the first successful result.
Each branch may parse to its own intermediate type before mapping into the shared result type.
type Answer = either {
.yes!,
.text String,
}
def AnswerFormat = Json.Union(type Answer, *(
(Json.Literal(.string "yes")) box [!] .yes!,
(Json.String) box [text] .text text,
))