Overview
Records to JSON and back: two traits, and a macro that writes their instances for a record.
(import (text json))
(import (text json-codec))
(def/json-type
(: Package (Record (: name string)
(: version string #:default "0.1.0")
(: homepage (Option string) #:optional)
(: dependencies (Vec string) #:json "deps" #:default []))))
(match (parse-json "{\"name\": \"http\", \"deps\": [\"net\"]}")
((Ok j) (json->Package j)) ; (Ok (Package (name "http") (version "0.1.0") ...))
((Err why) (Err why)))
->json turns a value into a Json tree, and json->
reads one back, answering (Err why) when the tree is not the
shape the type wants. Instances come with the module for Json
itself, long, int, double, bool,
string, and for (Option %a), (Vec %a) and
(Map string %a) of anything that has one. A record gets its pair
from def/json-type; any other type can have them written by hand.
Null is None. An Option is null when it is
None, and the value itself when it is Some; nothing else
reads or writes null.
Numbers. An integer type reads only a JSON integer: 1.0
is refused for an int field. A double reads either.
Errors. An error names where it happened, as far as one level
down: Point.x: expected an integer, or
Point: the object has no "y" field. A path through several levels
of records is not given. A key in a Map prefixes its value’s
error.
Not here yet. (text json) has no writer, so an encoded
Json cannot yet be written out as text.
See also: def/json-type, ->json, json->
Reference
Functions
->json
function
(: ->json (-> %a Json))
A value as a Json tree.
x- The value.
- returns
- Its tree: a record is an object, an Option is null or its value, a Vec an array, a Map an object.
A trait. Its instances are those listed in the module’s doc, and the
ones def/json-type writes for a record. An int is written
as a JsonInt, a double as a JsonFloat.
See also: json->, def/json-type
json->
function
(: json-> (-> Json (Result string %a)))
A Json tree read as a value of a type.
j- The tree.
- returns
- The value, or why the tree is not that type's shape.
A trait. Which instance a call means is decided by the type it is used
at; where nothing says, write the type’s own reader instead, such as
the json->Package that def/json-type declares. An
int is read only from a JsonInt that fits in one; a larger
number is refused rather than wrapped.
See also: ->json, def/json-type
json-object
function
(: json-object (-> string Json (Result string (Map string Json))))
(json-object owner j)
The fields of a tree that has to be an object. Called by the code def/json-type writes.
owner- The type being read, for the message.
j- The tree.
- returns
- The object's fields, or
Owner: expected an object.
Exported because the code def/json-type writes runs in the
module that uses it, and calls this by name. Not an API for people.
See also: json-field
json-field
function
(: json-field (-> string (Map string Json) string (Result string %a)))
(json-field owner m key)
A required field of an object, read as its type. Called by the code def/json-type writes.
owner- The type being read, for the message.
m- The object's fields.
key- The field's key.
- returns
- The value, or why not: a missing key, or the value's own error with
Owner.key:in front.
Exported because generated code calls it by name. Not an API for people.
See also: json-field/default, json-object
json-field/default
function
(: json-field/default (-> string (Map string Json) string %a (Result string %a)))
(json-field/default owner m key fallback)
A field that may be absent, read as its type. Called by the code def/json-type writes.
owner- The type being read, for the message.
m- The object's fields.
key- The field's key.
fallback- What an absent field, or one that is null, is read as.
- returns
- The value, the fallback, or the value's own error with
Owner.key:in front.
Exported because generated code calls it by name. Not an API for people.
See also: json-field
Macros
def/json-type
macro
(def/json-type declaration)
Declares a record, and the instances that write it as JSON and read it back.
declaration- A record declaration, as type takes one, whose fields may carry the markers below.
(def/json-type
(: (Page %a) (Record (: items (Vec %a))
(: next (Option string) #:optional)
(: total int #:json "totalCount"))))
(json->Page j) ; (Result string (Page %a)), given (json-> %a)
(Page->json page) ; Json
(def/json-type (: Name (Record (: field type marker ...) ...)))
declares the record Name as type would, then an
->json and a json-> instance for it, and two functions
that name the type: Name->json and json->Name. A type with
parameters, (: (Page %a) ...), gets instances that need the same
trait of each parameter.
A record is an object with a key for each field. The markers, after a field’s type:
#:json "key": the key in JSON, when it is not the field’s name.#:default expr: a field that may be absent, or null, and is thenexpr. It is still written.#:optional: a field whose type is anOption,Nonewhen it is absent or null, and left out when it isNone.#:mutable: kept, and passed on to the record.
A field without #:default or #:optional is required, and
reading an object without it is an error. Keys the record has no field
for are passed over.
The field types are not looked at: what a field is written as is
settled by the instance its type has, so a field of a type with no
->json is an error where the instance is wanted, not here.