sigildocs

(sigil json)

(sigil json) - JSON Serialization and Parsing

Streaming JSON encoding and decoding for Sigil. Provides bidirectional conversion between JSON and native Scheme data structures using ports for composability with sockets and other I/O.

Type Mapping

JSONScheme
objectdict: #{ name: "Alice" }
arrayarray: #[1 2 3]
stringstring: "hello"
numbernumber: 42, 3.14
true#t
false#f
null'null symbol

Encoding also accepts alists and lists for backwards compatibility.

Basic Usage

(import (sigil json))

;; Encode dict/array
(json-encode #{ name: "Alice" items: #[1 2 3] })
; => "{\"name\":\"Alice\",\"items\":[1,2,3]}"

;; Decode returns dict/array
(json-decode "{\"x\": 10, \"y\": 20}")
; => #{ x: 10 y: 20 }

;; Convert to alist if needed
(dict->alist (json-decode "{\"x\": 10}"))
; => ((x . 10))

Port-Based I/O

;; Write JSON to a port (streaming)
(call-with-output-file "data.json"
  (lambda (port)
    (json-write #{ name: "Alice" } port)))

;; Read JSON from a port
(call-with-input-file "data.json" json-read)

Pretty Printing

(json-encode #{ name: "Alice" age: 30 } indent: #t)
; => "{\n  \"name\": \"Alice\",\n  \"age\": 30\n}"

(json-encode #{ a: 1 } indent: 4)  ; 4-space indentation

Path Access

(define data (json-decode "{\"users\": [{\"name\": \"Alice\"}]}"))

(json-get-in data '(users: 0 name:))
; => "Alice"

(json-get-in data '(missing:) "default")
; => "default"

Exports

json-writeprocedure

Write a Scheme value as JSON to a port.

Writes JSON directly to the port without creating intermediate strings, making it suitable for streaming to files or sockets.

Use indent: for formatted output:

  • indent: #f - compact output (default)
  • indent: #t - indent with 2 spaces
  • indent: 4 - indent with 4 spaces
;; Write to stdout
(json-write #{ name: "Alice" } (current-output-port))
; outputs: {"name":"Alice"}

;; Write to a file
(call-with-output-file "data.json"
  (lambda (port)
    (json-write #{ name: "Alice" age: 30 } port indent: #t)))

;; Stream to a socket
(json-write response-data socket-port)

;; Pretty-print with custom indentation
(json-write data port indent: 4)
json-readprocedure

Read a JSON value from a port.

Parses one complete JSON value from the port and returns the corresponding Scheme value. JSON objects become dicts, arrays become Sigil arrays. The port is left positioned after the parsed value, allowing multiple values to be read from a stream.

;; Read from a string port
(call-with-input-string "{\"name\": \"Alice\"}" json-read)
; => #{ name: "Alice" }

;; Read from a file
(call-with-input-file "config.json" json-read)

;; Read arrays
(call-with-input-string "[1, 2, 3]" json-read)
; => #[1 2 3]

;; Read primitives
(call-with-input-string "true" json-read)   ; => #t
(call-with-input-string "null" json-read)   ; => null
(call-with-input-string "42" json-read)     ; => 42
json-encodeprocedure

Encode a Scheme value as a JSON string.

Converts a Scheme value to its JSON string representation. Returns compact JSON by default. Use indent: for formatted output:

  • indent: #t - indent with 2 spaces (default pretty)
  • indent: 4 - indent with 4 spaces
  • indent: #f - compact output (default)
;; Encode dicts as JSON objects
(json-encode #{ name: "Alice" age: 30 })
; => "{\"name\":\"Alice\",\"age\":30}"

;; Encode arrays
(json-encode #[1 2 3])
; => "[1,2,3]"

;; Encode primitives
(json-encode "hello")  ; => "\"hello\""
(json-encode 42)       ; => "42"
(json-encode #t)       ; => "true"
(json-encode 'null)    ; => "null"

;; Pretty-print with 2-space indentation
(json-encode #{ a: 1 b: #[1 2 3] } indent: #t)

;; Custom indentation (4 spaces)
(json-encode #{ x: 1 } indent: 4)
json-decodeprocedure

Decode a JSON string into a Scheme value.

Parses a JSON string and returns the corresponding Scheme value. JSON objects become dicts with keyword keys, arrays become Sigil arrays, and primitives map to their Scheme equivalents.

Use dict->alist to convert to traditional alists if needed.

;; Decode objects (become dicts)
(json-decode "{\"name\": \"Alice\", \"age\": 30}")
; => #{ name: "Alice" age: 30 }

;; Decode arrays (become arrays)
(json-decode "[1, 2, 3]")
; => #[1 2 3]

;; Decode nested structures
(json-decode "{\"user\": {\"name\": \"Bob\"}, \"scores\": [10, 20]}")
; => #{ user: #{ name: "Bob" } scores: #[10 20] }

;; Decode primitives
(json-decode "true")   ; => #t
(json-decode "false")  ; => #f
(json-decode "null")   ; => null
(json-decode "3.14")   ; => 3.14
(json-decode "\"hi\"") ; => "hi"

;; Convert to alist if needed
(dict->alist (json-decode "{\"x\": 1}"))
; => ((x . 1))
json-get-inprocedure

Get a nested value from a JSON structure using a path.

The path is a list of keys (keywords/strings for objects) and indices (integers for arrays). Returns the value at the path, or the default value (#f if not provided) if any key is missing or the structure doesn't match.

(define data (json-decode "{\"users\": [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]}"))

(json-get-in data '(users: 0 name:))
; => "Alice"

(json-get-in data '(users: 1 name:))
; => "Bob"

(json-get-in data '(users: 2 name:))
; => #f (index out of bounds)

(json-get-in data '(users: 2 name:) "unknown")
; => "unknown"

(json-get-in data '(missing:))
; => #f
json-null?procedure

Check if a value represents JSON null.

JSON null is represented as the symbol 'null in Scheme. Use this predicate to distinguish null from #f (JSON false).

(json-null? 'null)  ; => #t
(json-null? #f)     ; => #f
(json-null? '())    ; => #f

;; Check parsed JSON values
(json-null? (json-decode "null"))   ; => #t
(json-null? (json-decode "false"))  ; => #f