Rill v0.13 Reference

Standard library Β· Data formats

data/json

Imported as import "data/json" as json, its names are then json.…. Every signature below is the one the checker infers.

JSON, written in Rill: a recursive-descent parser and a printer.

Parsing functions return a Parsed β€” the value together with where it stopped, or why it stopped β€” because that is how a recursive-descent parser threads position through immutable data. A failure is a Parsed like any other and rides out through the same returns a value does; parse is the only one that turns it into a Result, because the caller is the only one with anywhere else to put it.

doc = json.parse("{\"user\": {\"name\": \"ada\", \"ids\": [1, 2]}}")?
json.path(doc, Cons("user", Cons("name", Nil)))          # => Some(json.JStr(ada))
json.as_str(json.path(doc, Cons("user", Cons("name", Nil))) |> unwrap_or(json.JNull))   # => ada
json.to_string(json.JObj(Cons(json.Field("n", json.JInt(5)), Nil)))   # => {"n":5}

Types

Json

  • JNull
  • JBool(Bool)
  • JInt(Int)
  • JNum(Float)
  • JStr(Str)
  • JArr(List(Json))
  • JObj(List(Field))

Field

  • Field(key: Str, value: Json)

Parsed

  • Parsed(value: Json, pos: Int)
  • Failed(why: Str, pos: Int)

Functions

fn parse(text: Str) -> Result(Json, Str)

A document is one value and then nothing but space. Text after that value means the document was not what it claimed to be, and a parser that returns the value anyway has answered a question nobody asked: nonsense is not the JSON document null with a remark after it.

json.parse("[1, 2.5, \"x\", true, null]")   # => Ok(json.JArr(Cons(json.JInt(1), Cons(json.JNum(2.5), Cons(json.JStr(x), Cons(json.JBool(true), Cons(json.JNull, Nil)))))))
json.parse("[1,]")                          # => Err(no value begins with `]`, at byte 3)
json.parse("null nonsense")                # => Err(trailing text, at byte 5)

fn number_of(text: Str) -> Json

JSON has a single number type, but a document id or a count is exact and a measurement is not. A literal that never showed a decimal point or an exponent stays an integer, so it prints back exactly the way it arrived β€” %g would turn an id of 1234567 into 1.23457e+06.

json.number_of("1234567")   # => json.JInt(1234567)
json.number_of("1.5e3")     # => json.JNum(1500)

fn is_number_char(c: Int) -> Bool

json.is_number_char("7"[0])   # => true
json.is_number_char("e"[0])   # => true
json.is_number_char(","[0])   # => false

fn to_string(j: Json) -> Str

Written into one builder rather than assembled out of pieces: the old printer built a string per value and a list of them per array, then joined those β€” so a document of six hundred records made thousands of strings on the way to one. Nothing here makes a string but the builder.

json.to_string(json.JArr(Cons(json.JInt(1), Cons(json.JStr("a\"b"), Nil))))   # => [1,"a\"b"]
json.to_string(json.JObj(Nil))   # => {}

fn get(j: Json, key: Str) -> Option(Json)

doc = json.parse("{\"a\": 1}")?
json.get(doc, "a")   # => Some(json.JInt(1))
json.get(doc, "b")   # => None

fn items(j: Json) -> List(Json)

json.items(json.parse("[1, 2]")?)   # => Cons(json.JInt(1), Cons(json.JInt(2), Nil))
json.items(json.JNull)                # => Nil

fn as_str(j: Json) -> Str

json.as_str(json.JStr("x"))   # => x
json.as_str(json.JInt(1))     # =>

fn as_float(j: Json) -> Float

json.as_float(json.JNum(2.5))   # => 2.5
json.as_float(json.JInt(2))     # => 2

fn as_int(j: Json) -> Int

For values that arrived as integers β€” ids, counts, versions. A JNum has no exact integer to give back, so it answers 0 rather than rounding silently.

json.as_int(json.JInt(7))     # => 7
json.as_int(json.JNum(7.9))   # => 0

fn as_bool(j: Json) -> Bool

json.as_bool(json.JBool(true))   # => true
json.as_bool(json.JNull)         # => false

fn path(j: Json, keys: List(Str)) -> Option(Json)

Walks a path of object keys: path(doc, Cons("user", Cons("name", Nil))).

doc = json.parse("{\"user\": {\"name\": \"ada\"}}")?
json.path(doc, Cons("user", Cons("name", Nil)))   # => Some(json.JStr(ada))
json.path(doc, Cons("user", Cons("age", Nil)))    # => None

fn parsed_or(text: Str, fallback: Json) -> Json

For a caller that has decided a bad document is simply an absent one β€” a cache entry, an optional config. Anything that answers a user wants the Err and the byte it names.

json.parsed_or("{\"a\": 1}", json.JNull)   # => json.JObj(Cons(json.Field(a, json.JInt(1)), Nil))
json.parsed_or("not json", json.JNull)   # => json.JNull