Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Json tree and a total decode/encode pair for payloads whose shape you do not control. decode never partially succeeds: a malformed input is one JsonError carrying 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 the ToJson/FromJson classes with instances for the base types and the containers, so a declared type converts to and from a Json tree structurally. (Derivation is a manual typeclass rather than deriving (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 (Int is arbitrary precision, so no integer overflows); a number decodes to JFloat only 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 under decode, and accepted, normalized, under decode_lossy. No silent rounding, no silent 1e999-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 a Json value has a well-defined hash and diff, and decode(encode(v)) returns v (with the one deliberate normalization that an integer-valued JFloat prints as an integer and decodes back to JInt, 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.