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))))