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 3 ;; JSON parser generator 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 9 ;; It's object-based (like CL-JSON) instead of using a 13 (in-package :dat/json) 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. 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." 24 (defun json-trailing-commas-p () *allow-json-trailing-commas*) 26 (defsetf json-trailing-commas-p () (val) 27 `(setq *allow-json-trailing-commas* ,val)) 29 (defclass json-object () 30 ((members :initform nil 32 :accessor json-object-members)) 33 (:documentation "An associative list of key/value pairs.")) 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)))) 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=))) 46 (values (second place) t)))) 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=))) 53 (let ((k (if (stringp key) 55 (princ-to-string key)))) 56 (push (list k value) (json-object-members object))) 57 (rplacd place (list value)))))) 59 (defsetf json-getf json-setf) 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)))) 67 (defmethod deserialize ((obj string) (format (eql :json)) &key (start 0) end) 68 (declare (ignore format)) 69 (json-decode obj :start start :end end)) 71 (defun json-encode (value &optional stream) 72 "Encodes a Lisp value into a stream." 73 (json-write value stream)) 75 (defmethod serialize (obj (format (eql :json)) &key stream) 76 (declare (ignore format)) 78 (json-encode obj stream) 79 (with-output-to-string (s) 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))) 92 ;; stop when nothing is left 93 unless (or xs key value) 94 return (make-instance 'json-object :members pairs) 96 ;; build associative list of key/value pairs 97 collect (list (princ-to-string key) value) 99 (set-dispatch-macro-character #\# #\{ #'json-object-reader) 100 (set-macro-character #\} (get-macro-character #\) nil)))) 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))) 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)) 115 (otherwise (json-read-number stream))))) 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) 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) 128 (error "JSON error: unexpected ~s" (read-char stream)))) 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)) 137 (defun json-read-false (stream) 138 "Read false from a JSON stream." 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))) 146 (defun json-read-null (stream) 147 "Read null from a JSON stream." 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))) 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)) 170 ;; next digit, test for eof 171 (unless (setf c (read-char stream nil)) 175 (unless (digit-char-p c) 176 (return (unread-char c stream)))))) 178 ;; fractional component 180 (when (equal (peek-char nil stream nil) #\.) 181 (write-char (read-char stream) s) 186 (when (equalp (peek-char nil stream nil) #\e) 187 (write-char (read-char stream) s) 190 (case (peek-char nil stream) 191 (#\- (write-char (read-char stream) s)) 192 (#\+ (write-char (read-char stream) s))) 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) 204 (read-from-string s)))) 206 (defun json-read-string (stream) 207 "Read a string from a JSON stream." 208 (declare (optimize (speed 3) (debug 0))) 210 ;; read the expected quote 211 (json-read-char stream #\" :skip-ws t) 213 ;; read into an output buffer 214 (with-output-to-string (s) 216 for c = (read-char stream) 218 ;; stop at closing quote 221 ;; write character to output 222 do (if (char/= c #\\) 224 (let ((c (case (read-char stream) 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) 241 ;; verbatim character 243 (write-char c s)))))) 245 (defun json-read-list (stream) 246 "Read a list of JSON values." 247 (declare (optimize (speed 3) (debug 0))) 249 ;; read the expected open bracket 250 (json-read-char stream #\[ :skip-ws t) 252 ;; check for an empty list 253 (if (json-peek-char stream #\] :skip-ws t) 256 for x = (json-read stream) 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))) 263 ;; return the final list 264 finally (return (prog1 xs 265 (json-read-char stream #\] :skip-ws t)))))) 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))) 271 ;; read the expected open brace 272 (json-read-char stream #\{ :skip-ws t) 274 ;; check for an empty object 275 (if (json-peek-char stream #\} :skip-ws t) 276 (make-instance 'json-object) 278 for key = (json-read-string stream) 280 (json-read-char stream #\: :skip-ws t) 283 ;; build the associative list of members 284 collect (list key value) 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))) 291 ;; return the final list 292 finally (return (prog1 (make-instance 'json-object :members xs) 293 (json-read-char stream #\} :skip-ws t)))))) 295 (defmethod json-write ((value (eql t)) &optional stream) 296 "Encode the true value." 297 (declare (ignore value)) 298 (format stream "~<true~>")) 300 (defmethod json-write ((value (eql nil)) &optional stream) 301 "Encode the null constant." 302 (declare (ignore value)) 303 (format stream "~<null~>")) 305 (defmethod json-write ((value symbol) &optional stream) 306 "Encode a symbol to a stream." 307 (json-write (symbol-name value) stream)) 309 (defmethod json-write ((value number) &optional stream) 310 "Encode a number to a stream." 311 (format stream "~<~a~>" value)) 313 (defmethod json-write ((value ratio) &optional stream) 314 "Encode a ratio to a stream." 315 (format stream "~<~a~>" (float value))) 317 (defmethod json-write ((value string) &optional stream) 318 "Encode a string as a stream." 319 (flet ((encode-char (c) 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") 329 (format nil "\\u~16,4,'0r" (char-code c))) 332 (format stream "~<\"~{~a~}\"~>" (map 'list #'encode-char value)))) 334 (defmethod json-write ((value pathname) &optional stream) 335 "Encode a pathname as a stream." 336 (json-write (namestring value) stream)) 338 (defmethod json-write ((value vector) &optional stream) 339 "Encode an array to a stream." 340 (let ((*print-pretty* t) 343 (*print-right-margin* 72)) 344 (pprint-logical-block (stream nil :prefix "[" :suffix "]") 345 (when (plusp (length value)) 346 (json-write (aref value 0))) 348 for i from 1 below (length value) 350 (write-char #\, stream) 351 (pprint-newline :fill) 352 (pprint-indent :block 0) 353 (json-write (aref value i) stream)))))) 355 (defmethod json-write ((value list) &optional stream) 356 "Encode a list to a stream." 357 (let ((*print-pretty* t) 360 (*print-right-margin* 72)) 361 (pprint-logical-block (stream value :prefix "[" :suffix "]") 362 (pprint-exit-if-list-exhausted) 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))))) 370 (defmethod json-write ((value hash-table) &optional stream) 371 "Encode a hash-table to a stream." 372 (let ((*print-pretty* t) 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) 380 (let ((key (pprint-pop))) 381 (if (not (stringp key)) 383 (warn "~s is not a valid JSON key; skipping...~%" key) 384 (pprint-exit-if-list-exhausted)) 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))))))))) 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) 399 (*print-right-margin* 72)) 400 (pprint-logical-block (stream (json-object-members value) 403 (pprint-exit-if-list-exhausted) 405 (let ((kv-pair (pprint-pop))) 406 (destructuring-bind (k v) 408 (if (not (stringp k)) 410 (warn "~s is not a valid JSON key; skipping...~%" k) 411 (pprint-exit-if-list-exhausted)) 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)))))))))