(text json)

Reference

Overview

JSON, read into a tree: RFC 8259, one character at a time.

(import (text json))

(match (parse-json "{\"name\": \"http\", \"tags\": [\"net\", \"web\"]}")
  ((Ok (JsonObj m)) (map-try-ref m "name"))   ; (Some (JsonStr "http"))
  ((Ok other) None)
  ((Err why) (println why) None))

A document is read into a Json, a union with a case for each kind of JSON value. Reading a record straight from JSON, and writing one as JSON, is (text json-codec)’s: its def/json-type declares a record and the instances that convert it.

What is read. Exactly RFC 8259: one value, with whitespace around it and nothing else after it. A byte order mark before the value is skipped. There are no comments, no trailing commas, no single quotes and no unquoted keys. A number may not have a leading zero, and needs digits after its decimal point and its exponent. A control character in a string has to be escaped, and a \u escape for a surrogate has to be one half of a pair of them; the pair is read as one character. A document is UTF-8, as RFC 8259 requires: bytes that are not are refused, at the line and column where they are.

How it reads. Off the document’s bytes, a chunk at a time: read-some from a port, or the string’s own bytes for parse-json. Outside a string everything JSON says is ASCII, so a byte is a character there; inside one, a character of several bytes is checked and copied whole.

Numbers. A number without a fraction or an exponent is a JsonInt, a long. One with either, or one too wide for a long, is a JsonFloat. So 1 and 1.0 are different values in the tree; (text json-codec) reads either into a double, and only the first into an integer.

Objects. An object is a (Map string Json), so the order its keys were written in is not kept, and when a key is written twice the last value wins.

Errors. A document that is not JSON is an Err saying where and why: line 1, column 7: ']' does not begin a value. A column is a character, so one outside the BMP is one column. Nothing is raised. A document nested deeper than json-max-depth is refused the same way rather than exhausting the stack.

Writing. json->string and write-json write a Json as text, and written does too. What they write reads back as the same value: a JsonFloat always has a point or an exponent, so 2.0 stays a float, and a double is written with the fewest digits that read back as the same double. The keys of an object are written in sorted order, because a Map does not keep the order they were read in. So the same value always gives the same text.

See also: Json, parse-json, read-json

Reference

Types

Functions

Values

Types

Json

union ↑ Types

A JSON value.

JsonNull
null.
JsonBool
true or false.
JsonInt
A number with no fraction and no exponent that fits in a long.
JsonFloat
Any other number.
JsonStr
A string, its escapes resolved.
JsonArr
An array, in order.
JsonObj
An object, by key. The order of the keys is not kept; a key written twice keeps its last value.

See also: parse-json

Functions

read-json

function ↑ Functions

(: read-json (-> InputPort (Result string Json)))
(read-json port)

Reads one JSON document from a port.

port
The port. Everything left in it is read: one value, and only space after it.
returns
The value, or why the text is not JSON, with its line and column.

A byte order mark before the value is skipped and does not count as a column. On a bjoroutine the port is read without blocking the thread.

See also: parse-json

json->string

function ↑ Functions

(: json->string (-> Json (#:indent int) (#:ascii bool) string))
(json->string j #:indent 0 #:ascii #f)

JSON text for a value: compact, or indented.

j
The value.
#:indent
Spaces for each level. 0, the default, writes everything on one line.
#:ascii
When #t, each character outside ASCII is written as a \u escape, and a character outside the BMP as a surrogate pair of them.
returns
The text. parse-json reads it back as the same value.
(json->string (JsonObj (map-set (map-set (map-empty) "b" (JsonInt 1)) "a" (JsonFloat 2.0))))
;; "{\"a\":2.0,\"b\":1}"

(json->string (JsonArr [(JsonStr "knee") JsonNull]) #:indent 2)
;; "[\n  \"knee\",\n  null\n]"

", \\ and the control characters are escaped in a string, as RFC 8259 requires. Nothing else is, unless #:ascii #t.

A JsonFloat that is NaN or infinite has no JSON form, and raises ArgumentException: that is a mistake in the program that made it.

See also: write-json, parse-json

write-json

function ↑ Functions

(: write-json (-> OutputPort Json (#:indent int) (#:ascii bool) void))
(write-json port j #:indent 0 #:ascii #f)

Writes the text json->string gives to a port.

port
The port.
j
The value.
#:indent
As for json->string.
#:ascii
As for json->string.

The text is made first, and written in one piece. On a bjoroutine the write does not block the thread.

See also: json->string, read-json

parse-json

function ↑ Functions

(: parse-json (-> string (Result string Json)))
(parse-json text)

Reads one JSON document from a string.

text
The document.
returns
The value, or why the text is not JSON, with its line and column.
(parse-json "[1, 2.5, null]")   ; (Ok (JsonArr [(JsonInt 1) (JsonFloat 2.5) JsonNull]))
(parse-json "[1, 2,]")          ; (Err "line 1, column 7: ']' does not begin a value")

See also: read-json

Values

json-max-depth

value ↑ Values

(: json-max-depth int)

How deep arrays and objects may nest before reading refuses the document: 1000.

See also: read-json