1 ;;;; json.scm --- JSON reader/writer
2 ;;;; Copyright © 2015, 2017 David Thompson <davet@gnu.org>
3 ;;;; Copyright © 2017 Christopher Allan Webber <cwebber@dustycloud.org>
5 ;;;; This library is free software; you can redistribute it and/or
6 ;;;; modify it under the terms of the GNU Lesser General Public
7 ;;;; License as published by the Free Software Foundation; either
8 ;;;; version 3 of the License, or (at your option) any later version.
10 ;;;; This library is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;;;; Lesser General Public License for more details.
15 ;;;; You should have received a copy of the GNU Lesser General Public
16 ;;;; License along with this library; if not, write to the Free Software
17 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 (define-module (aws utils json
)
21 #:use-module
(ice-9 match
)
22 #:export
(read-json write-json
))
24 (define (json-error port
)
25 (throw 'json-error port
))
27 (define (assert-char port char
)
28 "Read a character from PORT and throw an invalid JSON error if the
29 character is not CHAR."
30 (unless (eqv?
(read-char port
) char
)
33 (define (whitespace? char
)
34 "Return #t if CHAR is a whitespace character."
35 (char-set-contains? char-set
:whitespace char
))
37 (define (consume-whitespace port
)
38 "Discard characters from PORT until a non-whitespace character is
40 (match (peek-char port
)
41 ((? eof-object?
) *unspecified
*)
44 (consume-whitespace port
))
47 (define (make-keyword-reader keyword value
)
48 "Parse the keyword symbol KEYWORD as VALUE."
49 (let ((str (symbol->string keyword
)))
53 ((= i
(string-length str
)) value
)
54 ((eqv?
(string-ref str i
) (read-char port
))
56 (else (json-error port
)))))))
58 (define read-true
(make-keyword-reader 'true
#t
))
59 (define read-false
(make-keyword-reader 'false
#f
))
60 (define read-null
(make-keyword-reader 'null
'null
))
62 (define (read-hex-digit port
)
63 "Read a hexadecimal digit from PORT."
64 (match (read-char port
)
81 (_ (json-error port
))))
83 (define (read-utf16-character port
)
84 "Read a hexadecimal encoded UTF-16 character from PORT."
86 (+ (* (read-hex-digit port
) (expt 16 3))
87 (* (read-hex-digit port
) (expt 16 2))
88 (* (read-hex-digit port
) 16)
89 (read-hex-digit port
))))
91 (define (read-escape-character port
)
92 "Read escape character from PORT."
93 (match (read-char port
)
102 (#\u (read-utf16-character port
))
103 (_ (json-error port
))))
105 (define (read-string port
)
106 "Read a JSON encoded string from PORT."
107 (assert-char port
#\
")
108 (let loop ((result '()))
109 (match (read-char port)
110 ((? eof-object?) (json-error port))
111 (#\" (list->string
(reverse result
)))
112 (#\\
(loop (cons (read-escape-character port
) result
)))
113 (char (loop (cons char result
))))))
115 (define char-set
:json-digit
116 (char-set #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
118 (define (digit? char
)
119 (char-set-contains? char-set
:json-digit char
))
121 (define (read-digit port
)
122 "Read a digit 0-9 from PORT."
123 (match (read-char port
)
134 (else (json-error port
))))
136 (define (read-digits port
)
137 "Read a sequence of digits from PORT."
138 (let loop
((result '()))
139 (match (peek-char port
)
143 (loop (cons (read-digit port
) result
)))
144 (else (reverse result
)))))
146 (define (list->integer digits
)
147 "Convert the list DIGITS to an integer."
148 (let loop
((i (1- (length digits
)))
155 (+ result
(* n
(expt 10 i
)))
158 (define (read-positive-integer port
)
159 "Read a positive integer with no leading zeroes from PORT."
160 (match (read-digits port
)
162 (json-error port
)) ; no leading zeroes allowed
164 (list->integer digits
))))
166 (define (read-exponent port
)
167 "Read exponent from PORT."
169 (list->integer
(read-digits port
)))
171 (unless (memv (read-char port
) '(#\e #\E))
174 (match (peek-char port
)
185 (_ (json-error port
))))
187 (define (read-fraction port
)
188 "Read fractional number part from PORT as an inexact number."
189 (let* ((digits (read-digits port
))
190 (numerator (list->integer digits
))
191 (denomenator (expt 10 (length digits
))))
192 (/ numerator denomenator
)))
194 (define (read-positive-number port
)
195 "Read a positive number from PORT."
196 (let* ((integer (match (peek-char port
)
203 (read-positive-integer port
))
204 (_ (json-error port
))))
205 (fraction (match (peek-char port
)
208 (read-fraction port
))
210 (exponent (match (peek-char port
)
212 (read-exponent port
))
214 (n (* (+ integer fraction
) (expt 10 exponent
))))
216 ;; Keep integers as exact numbers, but convert numbers encoded as
217 ;; floating point numbers to an inexact representation.
220 (exact->inexact n
))))
222 (define (read-number port
)
223 "Read a number from PORT"
224 (match (peek-char port
)
229 (- (read-positive-number port
)))
231 (read-positive-number port
))
232 (_ (json-error port
))))
234 (define (read-object port
)
235 "Read key/value map from PORT."
236 (define (read-key+value-pair
)
237 (let ((key (read-string port
)))
238 (consume-whitespace port
)
239 (assert-char port
#\
:)
240 (consume-whitespace port
)
241 (let ((value (read-value port
)))
244 (assert-char port
#\
{)
245 (consume-whitespace port
)
247 (if (eqv?
#\
} (peek-char port
))
251 (cons (read-key+value-pair
)
253 (consume-whitespace port
)
254 (match (peek-char port
)
255 (#\
, ; read another value
257 (consume-whitespace port
)
258 (cons (read-key+value-pair
) (loop)))
262 (_ (json-error port
)))))))
264 (define (read-array port
)
265 "Read array from PORT."
266 (assert-char port
#\
[)
267 (consume-whitespace port
)
270 (if (eqv?
#\
] (peek-char port
))
274 (cons (read-value port
)
276 (consume-whitespace port
)
277 (match (peek-char port
)
278 (#\
, ; read another value
280 (consume-whitespace port
)
281 (cons (read-value port
) (loop)))
285 (_ (json-error port
))))))))
287 (define (read-value port
)
288 "Read a JSON value from PORT."
289 (consume-whitespace port
)
290 (match (peek-char port
)
291 ((? eof-object?
) (json-error port
))
292 (#\
" (read-string port))
293 (#\{ (read-object port))
294 (#\[ (read-array port))
295 (#\t (read-true port))
296 (#\f (read-false port))
297 (#\n (read-null port))
300 (_ (json-error port))))
302 (define (read-json port)
303 "Read JSON text from port and return an s-expression representation.
"
304 (let ((result (read-value port)))
305 (consume-whitespace port)
306 (unless (eof-object? (peek-char port))
316 (define (write-string str port)
317 "Write STR to PORT in JSON string format.
"
318 (define (escape-char char)
332 (string-for-each escape-char str
)
335 (define (write-object object port)
336 "Write ALIST to PORT in JSON object format.
"
337 ;; Keys may be strings or symbols.
340 ((? string? key) key)
341 ((? symbol? key) (symbol->string key))))
343 (define (write-kv-pair key value)
344 (write-string (key->string key) port)
346 (write-json value port))
351 ((front ... (end-key . end-val))
352 (for-each (match-lambda
354 (write-kv-pair key value)
357 (write-kv-pair end-key end-val)))
360 (define (write-array lst port)
361 "Write LST to PORT in JSON array format.
"
366 (for-each (lambda (val)
367 (write-json val port)
370 (write-json end port)))
373 (define (write-json exp port)
374 "Write EXP to PORT in JSON format.
"
376 (#t (display "true
" port))
377 (#f (display "false
" port))
378 ('null (display "null
" port))
379 ((? string? s) (write-string s port))
380 ((? real? n) (display n port))
381 (('@ . alist) (write-object alist port))
382 ((vals ...) (write-array vals port))))