std.json

spork-lang 0.6.0

Encode and decode Spork persistent values with Python-compatible JSON behavior.

The std.json namespace serializes and parses JSON while converting persistent Spork collections, keywords, and symbols at the boundary. Require it explicitly when source code needs JSON text or files.

Usage: (ns my-file (:require [std.json :as json]))

Encoding#

json.dumps uses Python's standard single-line JSON formatting. json.dumps-pretty uses an indentation level of two. json.generate is an alias for json.dumps.

(def encoded (json.dumps {:name "Spork" :items [1 2 3]}))
; typical encoded value: "{\"name\": \"Spork\", \"items\": [1, 2, 3]}"
; JSON object key order follows the map's unspecified iteration order
(json.loads encoded true) ; => {:name "Spork" :items [1 2 3]}

(json.dumps-pretty {:ready true})
; => "{\n  \"ready\": true\n}"

(json.generate {:status "ok"})
; => "{\"status\": \"ok\"}"

The encoder converts values recursively:

Spork valueJSON representation
nilnull
booleans, numbers, and stringscorresponding JSON scalar
Mapobject; keyword and symbol keys use their names, and every other non-string key is stringified
Vector, DoubleVector, IntVector, SortedVectorarray
Setarray; order is unspecified
Consarray
TransientMap, TransientVector, TransientSettheir current mutable contents
keyword used as a valuestring with a leading :
symbol used as a valuestring

json.dump and json.dump-pretty write to a file-like object and return nil:

(with [out (open "data.json" "w")]
  (json.dump-pretty {:name "Spork" :ready true} out))

(json.loads (.read-text #p"data.json") true)
; => {:name "Spork" :ready true}

Decoding#

json.loads parses a string and recursively converts JSON objects to persistent Map values and arrays to persistent Vector values. json.parse is an alias for json.loads.

(def data (json.loads "{\"name\": \"Spork\", \"items\": [1, 2]}"))
(get data "name")             ; => "Spork"
(get data "items")            ; => [1 2]

; Pass true to convert object keys to keywords at every nesting level
(def keyed (json.loads "{\"ready\": true}" true))
(:ready keyed)                 ; => true

(json.parse "[1, 2, 3]")      ; => [1 2 3]

json.load reads from a file-like object and accepts the same optional keywordization flag:

(with [in (open "data.json" "r")]
  (json.load in true))
; => {:ready true}

JSON has no set, symbol, or keyword type, so those distinctions do not round-trip automatically. Stringification can also make distinct map keys collide in a JSON object. Invalid JSON and unsupported encoded values raise the corresponding Python json exceptions.