Skip to content
heff

json

JSON (RFC 8259), written in heff: a value, read from its text (parse), written back (stringify, as JavaScript’s JSON.stringify writes it), read into (field, at, str, …) and built (obj, kv, num, jstr). Import it by a name, import json as JSON, as its at, int and str are short.

import json as JSON
-- | >>> uri "{\"params\": {\"textDocument\": {\"uri\": \"file:///a.hf\"}}}"
-- | Just ("file:///a.hf")
uri : String -> Maybe String
uri body =
either (\e -> Nothing) (\j -> JSON.str ["params", "textDocument", "uri"] j) (JSON.parse body)

A number without a fraction or an exponent is a JInt, of any size, as an Int is; one with either, a JFloat, the float nearest it (as C’s strtod reads it), written so that it is read back the very float — but an infinity or NaN, which JSON has no number for, written null, as JavaScript writes them. An object keeps its fields in order, and a key given twice twice. A string is text: an escape of a surrogate not in a pair is U+FFFD, as what is not UTF-8 is in a string heff reads.

Json

data Json
= JNull
| JBool Bool
| JInt Int
| JFloat Float64
| JString String
| JArray (List Json)
| JObject (List (String, Json))

A JSON value: a number without a fraction or an exponent a JInt, with either a JFloat; an object its fields in order, a key given twice twice.

stringify

stringify : Json -> String

The text of a value, as JavaScript’s JSON.stringify writes it: no space, an object’s fields in their order.

>>> stringify (JObject [MkPair "a" (JInt 1), MkPair "b" (JArray [JString "x\n", JNull, JBool True])])
"{\"a\":1,\"b\":[\"x\\n\",null,true]}"

(a float as JavaScript writes it, the fewest digits that read back as it, but -0.0 with its sign, so that parse reads back the very float; an infinity or NaN, which JSON has no number for, null, as JavaScript writes them)

>>> stringify (JArray [JFloat 1.0e300, JFloat (neg 0.0), JFloat (1.0 /. 0.0), JFloat (0.0 /. 0.0)])
"[1e+300,-0.0,null,null]"
>>> stringify (JArray [JFloat 1.5e-300, JFloat 5.0e-324, JFloat 1.0e21, JFloat 1.0e20, JFloat 0.5])
"[1.5e-300,5e-324,1e+21,100000000000000000000.0,0.5]"

(a string’s text: what is not UTF-8 in it, U+FFFD)

>>> stringify (JString ("é😀" ^ strSlice "😀" 0 1 ^ "\t"))
"\"é😀\u{FFFD}\\t\""

repr

repr : Json -> String

instanceDebugJson

instance instanceDebugJson : Debug Json

A value as the source writes it.

>>> debug (JArray [JInt -1, JString "a", JObject [MkPair "b" JNull]] : Json)
"JArray [JInt (-1), JString \"a\", JObject [MkPair \"b\" (JNull)]]"

instanceShowJson

instance instanceShowJson : Show Json

A value as JSON writes it, stringify.

Miss

data Miss = Expected Int String

ParseError

type ParseError = { offset : Int, line : Int, column : Int, expected : String }

Where the text is not JSON: the byte at which it is not (offset, from 0), its line and its column there (from 1, the column counted in characters, as an editor counts them), and what was expected there.

instanceDebugParseError

instance instanceDebugParseError : Debug ParseError

A parse error as it is written.

>>> debug ({ offset = 3, line = 1, column = 4, expected = "a value" } : ParseError)
"{ offset = 3, line = 1, column = 4, expected = \"a value\" }"

describe

describe : ParseError -> String

What a parse error says: line 1, column 4: expected a value.

instanceShowParseError

instance instanceShowParseError : Show ParseError
>>> show ({ offset = 3, line = 1, column = 4, expected = "a value" } : ParseError)
"line 1, column 4: expected a value"

Frame

data Frame = InArray (List Json) | InObject (List (String, Json)) String

Step

data Step
= Value Int (List Frame)
| Got Json Int (List Frame)
| Finished Json
| Failed Miss

parse

parse : String -> Either ParseError Json

The value a text is, white space around it; or where it is not one. It reads as it goes, a value inside another kept on a list, not on the stack, so that however deep the text it does not overflow.

>>> parse " {\"a\": [1, -2.5e1, \"\\u00e9\\ud83d\\ude00\"], \"b\": null} "
Right (JObject [MkPair "a" (JArray [JInt 1, JFloat (-25.0), JString "é😀"]), MkPair "b" (JNull)])
>>> parse "[1, 2"
Left ({ offset = 5, line = 1, column = 6, expected = "',' or ']'" })
>>> either describe (\j -> "parsed") (parse "{\n \"é\": tru\n}")
"line 2, column 8: expected a value"

field

field : String -> Json -> Maybe Json

A field of an object.

at

at : List String -> Json -> Maybe Json

The value at a path of fields.

>>> at ["a", "b"] (JObject [MkPair "a" (JObject [MkPair "b" (JInt 1)])])
Just (JInt 1)

str

str : List String -> Json -> Maybe String

A string at a path; Nothing where it is missing or of another kind, as each reading at a path is.

>>> either (\e -> Nothing) (\j -> str ["params", "textDocument", "uri"] j) (parse "{\"params\": {\"textDocument\": {\"uri\": \"file:///a.hf\"}}}")
Just ("file:///a.hf")

int

int : List String -> Json -> Maybe Int

An integer at a path: a float’s, rounded down.

bool

bool : List String -> Json -> Maybe Bool

A boolean at a path.

arr

arr : List String -> Json -> Maybe (List Json)

An array at a path: its elements.

obj

obj : List (String, Json) -> Json

An object, of its fields in order.

kv

kv : String -> Json -> (String, Json)

A field of an object: its key and its value.

num

num : Int -> Json

An integer.

jstr

jstr : String -> Json

A string.