File size: 17,153 Bytes
43203b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 | ;;;; -*- Mode: LISP; Syntax: ANSI-COMMON-LISP; Base: 10; Package: JSON -*-
;;;; Copyright (c) 2006-2008 Henrik Hjelte
;;;; Copyright (c) 2008 Hans Hübner (marked parts)
;;;; All rights reserved.
;;;; See the file LICENSE for terms of use and distribution.
(in-package :json)
(defvar *json-output* (make-synonym-stream '*standard-output*)
"The default output stream for encoding operations.")
(define-condition unencodable-value-error (type-error)
((context :accessor unencodable-value-error-context :initarg :context))
(:documentation
"Signalled when a datum is passed to ENCODE-JSON (or another
encoder function) which actually cannot be encoded.")
(:default-initargs :expected-type t)
(:report
(lambda (condition stream)
(with-accessors ((datum type-error-datum)
(context unencodable-value-error-context))
condition
(format stream
"Value ~S is not of a type which can be encoded~@[ by ~A~]."
datum context)))))
(defun unencodable-value-error (value &optional context)
"Signal an UNENCODABLE-VALUE-ERROR."
(error 'unencodable-value-error :datum value :context context))
(defmacro with-substitute-printed-representation-restart ((object stream)
&body body)
"Establish a SUBSTITUTE-PRINTED-REPRESENTATION restart for OBJECT
and execute BODY."
`(restart-case (progn ,@body)
(substitute-printed-representation ()
(let ((repr (with-output-to-string (s)
(write ,object :stream s :escape nil)
nil)))
(write-json-string repr ,stream)))))
(defgeneric encode-json (object &optional stream)
(:documentation "Write a JSON representation of OBJECT to STREAM and
return NIL."))
(defun encode-json-to-string (object)
"Return the JSON representation of OBJECT as a string."
(with-output-to-string (stream)
(encode-json object stream)))
(defmethod encode-json (anything &optional (stream *json-output*))
"If OBJECT is not handled by any specialized encoder signal an error
which the user can correct by choosing to encode the string which is
the printed representation of the OBJECT."
(declare (ignore stream))
(unencodable-value-error anything 'encode-json))
(defmethod encode-json ((nr number) &optional (stream *json-output*))
"Write the JSON representation of the number NR to STREAM (or to
*JSON-OUTPUT*)."
(write-json-number nr stream))
(defmethod encode-json ((s string) &optional (stream *json-output*))
"Write the JSON representation of the string S to STREAM (or to
*JSON-OUTPUT*)."
(write-json-string s stream))
(defmethod encode-json ((c character) &optional (stream *json-output*))
"JSON does not define a character type, we encode characters as Strings."
(encode-json (string c) stream))
(defmethod encode-json ((s symbol) &optional (stream *json-output*))
"Write the JSON representation of the symbol S to STREAM (or to
*JSON-OUTPUT*). If S is boolean, a boolean literal is written.
Otherwise, the name of S is passed to *LISP-IDENTIFIER-NAME-TO-JSON*
and the result is written as String."
(let ((mapped (car (rassoc s +json-lisp-symbol-tokens+))))
(if mapped
(progn (write-string mapped stream) nil)
(let ((s (funcall *lisp-identifier-name-to-json* (symbol-name s))))
(write-json-string s stream)))))
;;; The code below is from Hans Hübner's YASON (with modifications).
(defvar *json-aggregate-context* nil
"NIL outside of any aggregate environment, 'ARRAY or 'OBJECT within
the respective environments.")
(defvar *json-aggregate-first* t
"T when the first member of a JSON Object or Array is encoded,
afterwards NIL.")
(defun next-aggregate-member (context stream)
"Between two members of an Object or Array, print a comma separator."
(if (not (eq context *json-aggregate-context*))
(error "Member encoder used ~:[outside any~;in inappropriate~] ~
aggregate environment"
*json-aggregate-context*))
(prog1 *json-aggregate-first*
(unless *json-aggregate-first*
(write-char #\, stream))
(setq *json-aggregate-first* nil)))
(defmacro with-aggregate ((context begin-char end-char
&optional (stream '*json-output*))
&body body)
"Run BODY to encode a JSON aggregate type, delimited by BEGIN-CHAR
and END-CHAR."
`(let ((*json-aggregate-context* ',context)
(*json-aggregate-first* t))
(declare (special *json-aggregate-context* *json-aggregate-first*))
(write-char ,begin-char ,stream)
(unwind-protect (progn ,@body)
(write-char ,end-char ,stream))))
(defmacro with-array ((&optional (stream '*json-output*)) &body body)
"Open a JSON Array, run BODY, then close the Array. Inside the BODY,
AS-ARRAY-MEMBER or ENCODE-ARRAY-MEMBER should be called to encode
Members of the Array."
`(with-aggregate (array #\[ #\] ,stream) ,@body))
(defmacro as-array-member ((&optional (stream '*json-output*))
&body body)
"BODY should be a program which encodes exactly one JSON datum to
STREAM. AS-ARRAY-MEMBER ensures that the datum is properly formatted
as a Member of an Array, i. e. separated by comma from any preceding
or following Member."
`(progn
(next-aggregate-member 'array ,stream)
,@body))
(defun encode-array-member (object &optional (stream *json-output*))
"Encode OBJECT as the next Member of the innermost JSON Array opened
with WITH-ARRAY in the dynamic context. OBJECT is encoded using the
ENCODE-JSON generic function, so it must be of a type for which an
ENCODE-JSON method is defined."
(next-aggregate-member 'array stream)
(encode-json object stream)
object)
(defun stream-array-member-encoder (stream
&optional (encoder #'encode-json))
"Return a function which takes an argument and encodes it to STREAM
as a Member of an Array. The encoding function is taken from the
value of ENCODER (default is #'ENCODE-JSON)."
(lambda (object)
(as-array-member (stream)
(funcall encoder object stream))))
(defmacro with-object ((&optional (stream '*json-output*)) &body body)
"Open a JSON Object, run BODY, then close the Object. Inside the BODY,
AS-OBJECT-MEMBER or ENCODE-OBJECT-MEMBER should be called to encode
Members of the Object."
`(with-aggregate (object #\{ #\} ,stream) ,@body))
(defmacro as-object-member ((key &optional (stream '*json-output*))
&body body)
"BODY should be a program which writes exactly one JSON datum to
STREAM. AS-OBJECT-MEMBER ensures that the datum is properly formatted
as a Member of an Object, i. e. preceded by the (encoded) KEY and
colon, and separated by comma from any preceding or following Member."
`(progn
(next-aggregate-member 'object ,stream)
(let ((key (encode-json-to-string ,key)))
(if (char= (aref key 0) #\")
(progn (write-string key ,stream) nil)
(encode-json key ,stream)))
(write-char #\: ,stream)
,@body))
(defun encode-object-member (key value
&optional (stream *json-output*))
"Encode KEY and VALUE as a Member pair of the innermost JSON Object
opened with WITH-OBJECT in the dynamic context. KEY and VALUE are
encoded using the ENCODE-JSON generic function, so they both must be
of a type for which an ENCODE-JSON method is defined. If KEY does not
encode to a String, its JSON representation (as a string) is encoded
over again."
(as-object-member (key stream)
(encode-json value stream))
value)
(defun stream-object-member-encoder (stream
&optional (encoder #'encode-json))
"Return a function which takes two arguments and encodes them to
STREAM as a Member of an Object (String : Value pair)."
(lambda (key value)
(as-object-member (key stream)
(funcall encoder value stream))))
;;; End of YASON code.
;;; You can use the streaming encoder above, or
;;; two differnet types of sexp based encoders below
(defun encode-json-list-guessing-encoder (s stream)
"Write the JSON representation of the list S to STREAM (or to
*JSON-OUTPUT*). If S is not encodable as a JSON Array, try to encode
it as an Object (per ENCODE-JSON-ALIST)."
(restart-case
(handler-bind ((unencodable-value-error
(lambda (e)
(with-accessors ((datum type-error-datum)) e
(if (and (consp datum)
(ignore-errors (every #'consp s)))
(invoke-restart 'try-as-alist)
(error e)))))
(type-error
(lambda (e)
(declare (ignore e))
(unencodable-value-error s 'encode-json))))
(write-string
(with-output-to-string (temp)
(with-array (temp)
(mapcar (stream-array-member-encoder temp) s)))
stream))
(try-as-alist ()
(encode-json-alist s stream)))
(values))
(defun json-bool (value)
"Intended for the JSON-EXPLICT-ENCODER. Converts a non-nil value
to a value (:true) that creates a json true value when used in the
explict encoder. Or (:false)."
(if value
'(:true)
'(:false)))
(defun json-or-null (value)
"Intended for the JSON-EXPLICT-ENCODER. Returns a non-nil value
as itself, or a nil value as a json null-value"
(or value '(:null)))
(defun encode-json-list-explicit-encoder (s stream)
(handler-bind ((type-error
(lambda (e)
(declare (ignore e))
(unencodable-value-error s 'encode-json))))
(ecase (car s)
(:json (mapcar (lambda (str) (write-string str stream))
(cdr s)))
(:true (write-json-chars "true" stream))
(:false (write-json-chars "false" stream))
(:null (write-json-chars "null" stream))
((:list :array)
(with-array (stream)
(mapcar (stream-array-member-encoder stream)
(cdr s))))
(:object (if (consp (cadr s))
(encode-json-alist (cdr s) stream)
(encode-json-plist (cdr s) stream)))
(:alist (encode-json-alist (cdr s) stream))
(:plist (encode-json-plist (cdr s) stream)))
nil))
(defparameter *json-list-encoder-fn* 'encode-json-list-guessing-encoder)
(defun use-guessing-encoder ()
(setf *json-list-encoder-fn* 'encode-json-list-guessing-encoder))
(defun use-explicit-encoder ()
(setf *json-list-encoder-fn* 'encode-json-list-explicit-encoder))
(defmacro with-local-encoder (&body body)
`(let (*json-list-encoder-fn*)
(declare (special *json-list-encoder-fn*))
,@body))
(defmacro with-guessing-encoder (&body body)
`(with-local-encoder (use-guessing-encoder)
,@body))
(defmacro with-explicit-encoder (&body body)
`(with-local-encoder (use-explicit-encoder)
,@body))
(defmethod encode-json ((s list) &optional (stream *json-output*))
"Write the JSON representation of the list S to STREAM (or to
*JSON-OUTPUT*), using one of the two rules specified by
first calling USE-GUESSING-ENCODER or USE-EXPLICIT-ENCODER.
The guessing encoder: If S is a list encode S as a JSON Array, if
S is a dotted list encode it as an Object (per ENCODE-JSON-ALIST).
The explicit decoder: If S is a list, the first symbol defines
the encoding:
If (car S) is 'TRUE return a JSON true value.
If (car S) is 'FALSE return a JSON false value.
If (car S) is 'NULL return a JSON null value.
If (car S) is 'JSON princ the strings in (cdr s) to stream
If (car S) is 'LIST or 'ARRAY encode (cdr S) as a a JSON Array.
If (car S) is 'OBJECT encode (cdr S) as A JSON Object,
interpreting (cdr S) either as an A-LIST or a P-LIST."
(funcall *json-list-encoder-fn* s stream))
(defmethod encode-json ((s sequence) &optional (stream *json-output*))
"Write the JSON representation (Array) of the sequence S (not an
alist) to STREAM (or to *JSON-OUTPUT*)."
(with-array (stream)
(map nil (stream-array-member-encoder stream) s)))
(defmethod encode-json ((h hash-table) &optional (stream *json-output*))
"Write the JSON representation (Object) of the hash table H to
STREAM (or to *JSON-OUTPUT*)."
(with-object (stream)
(maphash (stream-object-member-encoder stream) h)))
#+cl-json-clos
(defmethod encode-json ((o standard-object)
&optional (stream *json-output*))
"Write the JSON representation (Object) of the CLOS object O to
STREAM (or to *JSON-OUTPUT*)."
(with-object (stream)
(map-slots (stream-object-member-encoder stream) o)))
(defun encode-json-alist (alist &optional (stream *json-output*))
"Write the JSON representation (Object) of ALIST to STREAM (or to
*JSON-OUTPUT*). Return NIL."
(write-string
(with-output-to-string (temp)
(with-object (temp)
(loop
with bindings = alist
do (if (listp bindings)
(if (endp bindings)
(return)
(let ((binding (pop bindings)))
(if (consp binding)
(destructuring-bind (key . value) binding
(encode-object-member key value temp))
(unless (null binding)
(unencodable-value-error
alist 'encode-json-alist)))))
(unencodable-value-error alist 'encode-json-alist)))))
stream)
nil)
(defun encode-json-alist-to-string (alist)
"Return the JSON representation (Object) of ALIST as a string."
(with-output-to-string (stream)
(encode-json-alist alist stream)))
(defun encode-json-plist (plist &optional (stream *json-output*))
"Write the JSON representation (Object) of PLIST to STREAM (or to
*JSON-OUTPUT*). Return NIL."
(write-string
(with-output-to-string (temp)
(with-object (temp)
(loop
with properties = plist
do (if (listp properties)
(if (endp properties)
(return)
(let ((indicator (pop properties)))
(if (and (listp properties)
(not (endp properties)))
(encode-object-member
indicator (pop properties) temp)
(unencodable-value-error
plist 'encode-json-plist))))
(unencodable-value-error plist 'encode-json-plist)))))
stream)
nil)
(defun encode-json-plist-to-string (plist)
"Return the JSON representation (Object) of PLIST as a string."
(with-output-to-string (stream)
(encode-json-plist plist stream)))
(defun write-json-string (s stream)
"Write a JSON representation (String) of S to STREAM."
(write-char #\" stream)
(if (stringp s)
(write-json-chars s stream)
(encode-json s stream))
(write-char #\" stream)
nil)
(defun write-json-chars (s stream)
"Write JSON representations (chars or escape sequences) of
characters in string S to STREAM."
(loop for ch across s
for code = (char-code ch)
with special
if (setq special (car (rassoc ch +json-lisp-escaped-chars+)))
do (write-char #\\ stream) (write-char special stream)
else if (< #x1f code #x7f)
do (write-char ch stream)
else
do (let ((special '#.(rassoc-if #'consp +json-lisp-escaped-chars+)))
(destructuring-bind (esc . (width . radix)) special
(format stream "\\~C~V,V,'0R" esc radix width code)))))
(eval-when (:compile-toplevel :execute)
(if (subtypep 'long-float 'single-float)
;; only one float type
(pushnew :cl-json-only-one-float-type *features*)
;; else -- we check here only for the case where there are two
;; float types, single- and double- --- we don't consider the
;; "only single and short" case. Could be added if necessary.
(progn
(when (subtypep 'single-float 'short-float)
(pushnew :cl-json-single-float-is-subsumed *features*))
(when (subtypep 'long-float 'double-float)
(pushnew :cl-json-double-float-is-subsumed *features*)))))
(defun write-json-number (nr stream)
"Write the JSON representation of the number NR to STREAM."
(typecase nr
(integer (format stream "~d" nr))
(real (let ((*read-default-float-format*
(etypecase nr
(short-float 'short-float)
(rational 'single-float)
#-(or cl-json-single-float-is-subsumed
cl-json-only-one-float-type)
(single-float 'single-float)
#-(or cl-json-double-float-is-subsumed
cl-json-only-one-float-type)
(double-float 'double-float)
#-cl-json-only-one-float-type
(long-float 'long-float))))
(format stream "~f" nr)))
(t (unencodable-value-error nr 'write-json-number))))
|