Json
JSON: a dynamic value tree, a total parser, a canonical encoder, and a typed layer.
Two layers, following the wire discipline of lib/std/Wire.pr:
- The dynamic layer is the
Jsontree and a totaldecode/encodepair for payloads whose shape you do not control.decodenever partially succeeds: a malformed input is oneJsonErrorcarrying a message and a line/column, never a panic or a truncated value. Nesting is depth-limited, and any input left over after the top-level value is rejected. - The typed layer is theToJson/FromJsonclasses with instances for the base types and the containers, so a declared type converts to and from aJsontree structurally. (Derivation is a manual typeclass rather thanderiving (Json): the derivable set is fixed, so a new derivable class is a compiler change; the class-and-instances form is pure library code.)
The JSON contract is deliberately narrow:
- Number semantics are exact-or-error by default. An integer literal decodes to
JInt(Intis arbitrary precision, so no integer overflows); a number decodes toJFloatonly when its lexeme is already the canonical form the owned formatter would print (show_int/show_float). A non-canonical spelling (1.0,1e3,0.10) is a decode error underdecode, and accepted, normalized, underdecode_lossy. No silent rounding, no silent1e999-to-infinity. - Encoding is canonical and byte-deterministic: object keys are sorted, every number is printed by the owned formatter, strings use one fixed escaping, and there are no whitespace options. Two machines encoding equal values produce equal bytes, so aJsonvalue has a well-defined hash and diff, anddecode(encode(v))returnsv(with the one deliberate normalization that an integer-valuedJFloatprints as an integer and decodes back toJInt, since JSON does not distinguish the two).
Strings are validated UTF-8 by construction (the String type), so invalid UTF-8 is rejected at the byte boundary that builds the input, before the parser ever runs. Streaming (gigabyte, SAX-style) JSON is a package; this codec is value-oriented and total.
Types
Json
type Json
= JNull
| JBool(Bool)
| JInt(Int)
| JFloat(Float)
| JStr(String)
| JArr(List(Json))
| JObj(List((String, Json)))
A dynamic JSON value. Numbers split into JInt and JFloat so the exact-or-error decode is a structural distinction rather than a hidden flag: an object is an association list in parse order (duplicate keys are preserved, not merged).
JsonError
type JsonError = JsonError(String, Int, Int)
A decode failure: a human-readable message and the 1-based line and column of the offending byte.
Type Classes
ToJson
class ToJson(a)
to_json : (a) -> Json
Convert a value to a Json tree.
FromJson
class FromJson(a)
from_json : (Json) -> a ! {Fail | e}
Recover a value from a Json tree, failing (through Fail) on a structural mismatch. A decode of foreign data is one ordinary failure channel.
Instances
toJsonInt
instance toJsonInt : ToJson(Int)
toJsonFloat
instance toJsonFloat : ToJson(Float)
toJsonBool
instance toJsonBool : ToJson(Bool)
toJsonString
instance toJsonString : ToJson(String)
toJsonList
instance toJsonList : ToJson(List(a))
toJsonOption
instance toJsonOption : ToJson(Option(a))
toJsonPair
instance toJsonPair : ToJson((a, b))
fromJsonInt
instance fromJsonInt : FromJson(Int)
fromJsonFloat
instance fromJsonFloat : FromJson(Float)
fromJsonBool
instance fromJsonBool : FromJson(Bool)
fromJsonString
instance fromJsonString : FromJson(String)
fromJsonList
instance fromJsonList : FromJson(List(a))
fromJsonOption
instance fromJsonOption : FromJson(Option(a))
fromJsonPair
instance fromJsonPair : FromJson((a, b))
Functions and Values
json_error_message
json_error_message : (Json.JsonError) -> String
Render a JsonError as line L col C: message.
decode
decode : (String) -> Result(Json.Json, Json.JsonError)
Decode a JSON document with exact number semantics: a number decodes only when its lexeme is already canonical, otherwise a decode error. Total: any malformed or lossy input is an Err with a position.
decode_lossy
decode_lossy : (String) -> Result(Json.Json, Json.JsonError)
Decode a JSON document, accepting any well-formed number and normalizing it to JInt (exact integer in range) or the nearest JFloat. Still total, and still rejects structurally malformed input.
encode
encode : (Json.Json) -> String
Encode a Json value to its canonical byte-deterministic string: object keys sorted, numbers by the owned formatter, one fixed string escaping, no optional whitespace. Equal values encode to equal bytes.
to_json_string
to_json_string : forall a. (a) -> String
Encode a typed value straight to a canonical JSON string.