changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/dat/json.lisp

changeset 698: 96958d3eb5b0
parent: 5540a3e32ba1
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 ;;; lib/dat/json.lisp --- JSON format
2 
3 ;; JSON parser generator
4 
5 ;; There are quite a few json libraries in the CL ecosystem. This
6 ;; particular implementation is based on the JSON package here:
7 ;; https://github.com/massung/json
8 
9 ;; It's object-based (like CL-JSON) instead of using a
10 ;; parser-generator.
11 
12 ;;; Code:
13 (in-package :dat/json)
14 
15 (defvar *allow-json-trailing-commas* nil
16  "When non-nil, arrange for our json readers to allow trailing
17 commas. This binding does not affect writers.
18 
19 Trailing commas in json lists and objects is a common source of frustration
20 since they're not allowed in the spec. This is easily forgotten when
21 generating json from a scripting language without native json support."
22  )
23 
24 (defun json-trailing-commas-p () *allow-json-trailing-commas*)
25 
26 (defsetf json-trailing-commas-p () (val)
27  `(setq *allow-json-trailing-commas* ,val))
28 
29 (defclass json-object ()
30  ((members :initform nil
31  :initarg :members
32  :accessor json-object-members))
33  (:documentation "An associative list of key/value pairs."))
34 
35 (defmethod print-object ((obj json-object) stream)
36  "Output a JSON object to a stream in readable form."
37  (print-unreadable-object (obj stream :type t)
38  (let ((*print-level* 1))
39  (json-encode obj stream))))
40 
41 (defun json-getf (object key &optional value)
42  "Find an member's value in a JSON object."
43  (let ((place (assoc key (json-object-members object) :test 'string=)))
44  (if (null place)
45  value
46  (values (second place) t))))
47 
48 (defun json-setf (object key value)
49  "Assign a value to a key in a JSON object."
50  (let ((place (assoc key (json-object-members object) :test 'string=)))
51  (prog1 value
52  (if (null place)
53  (let ((k (if (stringp key)
54  key
55  (princ-to-string key))))
56  (push (list k value) (json-object-members object)))
57  (rplacd place (list value))))))
58 
59 (defsetf json-getf json-setf)
60 
61 (defun json-decode (string &key (start 0) end)
62  "Convert a JSON string into a Lisp object."
63  (with-input-from-string (stream string :start start :end end)
64  (values (json-read stream)
65  (file-position stream))))
66 
67 (defmethod deserialize ((obj string) (format (eql :json)) &key (start 0) end)
68  (declare (ignore format))
69  (json-decode obj :start start :end end))
70 
71 (defun json-encode (value &optional stream)
72  "Encodes a Lisp value into a stream."
73  (json-write value stream))
74 
75 (defmethod serialize (obj (format (eql :json)) &key stream)
76  (declare (ignore format))
77  (if stream
78  (json-encode obj stream)
79  (with-output-to-string (s)
80  (json-encode obj s)
81  s)))
82 
83 (defun json-enable-reader-macro ()
84  "Set the #{ dispatch macro character for reading JSON objects."
85  (flet ((json-object-reader (stream char n)
86  (declare (ignorable char n))
87  (let ((xs (read-delimited-list #\} stream t)))
88  (loop
89  for key = (pop xs)
90  for value = (pop xs)
91 
92  ;; stop when nothing is left
93  unless (or xs key value)
94  return (make-instance 'json-object :members pairs)
95 
96  ;; build associative list of key/value pairs
97  collect (list (princ-to-string key) value)
98  into pairs))))
99  (set-dispatch-macro-character #\# #\{ #'json-object-reader)
100  (set-macro-character #\} (get-macro-character #\) nil))))
101 
102 (defun json-read (stream &optional (eof-error-p t) eof-value)
103  "Read a JSON object from a stream."
104  (let ((c (peek-char t stream eof-error-p :eof)))
105  (case c
106  (:eof eof-value)
107  ;; constants, objects, lists, and strings
108  (#\t (json-read-true stream))
109  (#\f (json-read-false stream))
110  (#\n (json-read-null stream))
111  (#\{ (json-read-object stream))
112  (#\[ (json-read-list stream))
113  (#\" (json-read-string stream))
114  ;; must be a number
115  (otherwise (json-read-number stream)))))
116 
117 (defun json-peek-char (stream expected &key skip-ws)
118  "Peek at the next character or token and optionally error if unexpected."
119  (declare (optimize (speed 3) (debug 0)))
120  (when (equal (peek-char skip-ws stream) expected)
121  (read-char stream)))
122 
123 (defun json-read-char (stream expected &key skip-ws)
124  "Read the next, expected character in the stream."
125  (declare (optimize (speed 3) (debug 0)))
126  (if (json-peek-char stream expected :skip-ws skip-ws)
127  t
128  (error "JSON error: unexpected ~s" (read-char stream))))
129 
130 (defun json-read-true (stream)
131  "Read true from a JSON stream."
132  (json-read-char stream #\t :skip-ws t)
133  (json-read-char stream #\r)
134  (json-read-char stream #\u)
135  (json-read-char stream #\e))
136 
137 (defun json-read-false (stream)
138  "Read false from a JSON stream."
139  (prog1 nil
140  (json-read-char stream #\f :skip-ws t)
141  (json-read-char stream #\a)
142  (json-read-char stream #\l)
143  (json-read-char stream #\s)
144  (json-read-char stream #\e)))
145 
146 (defun json-read-null (stream)
147  "Read null from a JSON stream."
148  (prog1 nil
149  (json-read-char stream #\n :skip-ws t)
150  (json-read-char stream #\u)
151  (json-read-char stream #\l)
152  (json-read-char stream #\l)))
153 
154 (defun json-read-number (stream)
155  "Read a number from a JSON stream."
156  (declare (optimize (speed 3) (debug 0)))
157  (let ((s (with-output-to-string (s)
158  (when (equal (peek-char t stream) #\-)
159  (write-char (read-char stream) s))
160  ;; read base-10 digits, fraction, and exponent
161  (labels ((read-digits ()
162  (let ((c (read-char stream)))
163  (unless (digit-char-p c)
164  (error "JSON error: unexpected ~s" c))
165 
166  ;; write the digits
167  (loop
168  (write-char c s)
169 
170  ;; next digit, test for eof
171  (unless (setf c (read-char stream nil))
172  (return))
173 
174  ;; ensure digit
175  (unless (digit-char-p c)
176  (return (unread-char c stream))))))
177 
178  ;; fractional component
179  (read-fraction ()
180  (when (equal (peek-char nil stream nil) #\.)
181  (write-char (read-char stream) s)
182  (read-digits)))
183 
184  ;; exponent
185  (read-exponent ()
186  (when (equalp (peek-char nil stream nil) #\e)
187  (write-char (read-char stream) s)
188 
189  ;; optional sign
190  (case (peek-char nil stream)
191  (#\- (write-char (read-char stream) s))
192  (#\+ (write-char (read-char stream) s)))
193 
194  ;; exponent
195  (read-digits))))
196 
197  ;; read each component; numbers beginning with 0 are a special case
198  (if (equalp (peek-char nil stream) #\0)
199  (write-char (read-char stream) s)
200  (read-digits))
201  (read-fraction)
202  (read-exponent)))))
203  (prog1
204  (read-from-string s))))
205 
206 (defun json-read-string (stream)
207  "Read a string from a JSON stream."
208  (declare (optimize (speed 3) (debug 0)))
209 
210  ;; read the expected quote
211  (json-read-char stream #\" :skip-ws t)
212 
213  ;; read into an output buffer
214  (with-output-to-string (s)
215  (loop
216  for c = (read-char stream)
217 
218  ;; stop at closing quote
219  until (char= c #\")
220 
221  ;; write character to output
222  do (if (char/= c #\\)
223  (write-char c s)
224  (let ((c (case (read-char stream)
225  (#\n #\newline)
226  (#\t #\tab)
227  (#\f #\formfeed)
228  (#\b #\backspace)
229  (#\r #\return)
230 
231  ;; read unicode character
232  (#\u (let ((x1 (digit-char-p (read-char stream) 16))
233  (x2 (digit-char-p (read-char stream) 16))
234  (x3 (digit-char-p (read-char stream) 16))
235  (x4 (digit-char-p (read-char stream) 16)))
236  (code-char (logior (ash x1 12)
237  (ash x2 8)
238  (ash x3 4)
239  (ash x4 0)))))
240 
241  ;; verbatim character
242  (otherwise c))))
243  (write-char c s))))))
244 
245 (defun json-read-list (stream)
246  "Read a list of JSON values."
247  (declare (optimize (speed 3) (debug 0)))
248 
249  ;; read the expected open bracket
250  (json-read-char stream #\[ :skip-ws t)
251 
252  ;; check for an empty list
253  (if (json-peek-char stream #\] :skip-ws t)
254  nil
255  (loop
256  for x = (json-read stream)
257  collect x
258  into xs
259  ;; check for another element
260  while (and (json-peek-char stream #\, :skip-ws t)
261  (unless (and (json-trailing-commas-p) (equal #\] (peek-char t stream)))
262  t))
263  ;; return the final list
264  finally (return (prog1 xs
265  (json-read-char stream #\] :skip-ws t))))))
266 
267 (defun json-read-object (stream)
268  "Read an associative list of key/value pairs into a JSON object."
269  (declare (optimize (speed 3) (debug 0)))
270 
271  ;; read the expected open brace
272  (json-read-char stream #\{ :skip-ws t)
273 
274  ;; check for an empty object
275  (if (json-peek-char stream #\} :skip-ws t)
276  (make-instance 'json-object)
277  (loop
278  for key = (json-read-string stream)
279  for value = (progn
280  (json-read-char stream #\: :skip-ws t)
281  (json-read stream))
282 
283  ;; build the associative list of members
284  collect (list key value)
285  into xs
286 
287  ;; check for another element
288  while (and (json-peek-char stream #\, :skip-ws t)
289  (unless (and (json-trailing-commas-p) (equal #\} (peek-char t stream)))
290  t))
291  ;; return the final list
292  finally (return (prog1 (make-instance 'json-object :members xs)
293  (json-read-char stream #\} :skip-ws t))))))
294 
295 (defmethod json-write ((value (eql t)) &optional stream)
296  "Encode the true value."
297  (declare (ignore value))
298  (format stream "~<true~>"))
299 
300 (defmethod json-write ((value (eql nil)) &optional stream)
301  "Encode the null constant."
302  (declare (ignore value))
303  (format stream "~<null~>"))
304 
305 (defmethod json-write ((value symbol) &optional stream)
306  "Encode a symbol to a stream."
307  (json-write (symbol-name value) stream))
308 
309 (defmethod json-write ((value number) &optional stream)
310  "Encode a number to a stream."
311  (format stream "~<~a~>" value))
312 
313 (defmethod json-write ((value ratio) &optional stream)
314  "Encode a ratio to a stream."
315  (format stream "~<~a~>" (float value)))
316 
317 (defmethod json-write ((value string) &optional stream)
318  "Encode a string as a stream."
319  (flet ((encode-char (c)
320  (cond
321  ((char= c #\\) "\\\\")
322  ((char= c #\") "\\\"")
323  ((char= c #\newline) "\\n")
324  ((char= c #\tab) "\\t")
325  ((char= c #\formfeed) "\\f")
326  ((char= c #\backspace) "\\b")
327  ((char= c #\return) "\\r")
328  ((char> c #\~)
329  (format nil "\\u~16,4,'0r" (char-code c)))
330  (t
331  (string c)))))
332  (format stream "~<\"~{~a~}\"~>" (map 'list #'encode-char value))))
333 
334 (defmethod json-write ((value pathname) &optional stream)
335  "Encode a pathname as a stream."
336  (json-write (namestring value) stream))
337 
338 (defmethod json-write ((value vector) &optional stream)
339  "Encode an array to a stream."
340  (let ((*print-pretty* t)
341  (*print-length* nil)
342  (*print-lines* nil)
343  (*print-right-margin* 72))
344  (pprint-logical-block (stream nil :prefix "[" :suffix "]")
345  (when (plusp (length value))
346  (json-write (aref value 0)))
347  (loop
348  for i from 1 below (length value)
349  do (progn
350  (write-char #\, stream)
351  (pprint-newline :fill)
352  (pprint-indent :block 0)
353  (json-write (aref value i) stream))))))
354 
355 (defmethod json-write ((value list) &optional stream)
356  "Encode a list to a stream."
357  (let ((*print-pretty* t)
358  (*print-length* nil)
359  (*print-lines* nil)
360  (*print-right-margin* 72))
361  (pprint-logical-block (stream value :prefix "[" :suffix "]")
362  (pprint-exit-if-list-exhausted)
363  (loop
364  (json-write (pprint-pop) stream)
365  (pprint-exit-if-list-exhausted)
366  (write-char #\, stream)
367  (pprint-newline :fill)
368  (pprint-indent :block 0)))))
369 
370 (defmethod json-write ((value hash-table) &optional stream)
371  "Encode a hash-table to a stream."
372  (let ((*print-pretty* t)
373  (*print-length* nil)
374  (*print-lines* nil)
375  (*print-right-margin* 72))
376  (let ((keys (loop for key being each hash-keys in value collect key)))
377  (pprint-logical-block (stream keys :prefix "{" :suffix "}")
378  (pprint-exit-if-list-exhausted)
379  (loop
380  (let ((key (pprint-pop)))
381  (if (not (stringp key))
382  (progn
383  (warn "~s is not a valid JSON key; skipping...~%" key)
384  (pprint-exit-if-list-exhausted))
385  (progn
386  (json-write key stream)
387  (write-char #\: stream)
388  (json-write (gethash key value) stream)
389  (pprint-exit-if-list-exhausted)
390  (write-char #\, stream)
391  (pprint-newline :mandatory)
392  (pprint-indent :current 0)))))))))
393 
394 (defmethod json-write ((value json-object) &optional stream)
395  "Encode a JSON object with an associative list of members to a stream."
396  (let ((*print-pretty* t)
397  (*print-length* nil)
398  (*print-lines* nil)
399  (*print-right-margin* 72))
400  (pprint-logical-block (stream (json-object-members value)
401  :prefix "{"
402  :suffix "}")
403  (pprint-exit-if-list-exhausted)
404  (loop
405  (let ((kv-pair (pprint-pop)))
406  (destructuring-bind (k v)
407  kv-pair
408  (if (not (stringp k))
409  (progn
410  (warn "~s is not a valid JSON key; skipping...~%" k)
411  (pprint-exit-if-list-exhausted))
412  (progn
413  (json-write k stream)
414  (write-char #\: stream)
415  (json-write v stream)
416  (pprint-exit-if-list-exhausted)
417  (write-char #\, stream)
418  (pprint-newline :mandatory)
419  (pprint-indent :current 0)))))))))