[cl-json-devel] Re: proposed: improvements to encoder (was: decoder) customization

Boris Smilga boris.smilga at gmail.com
Wed Aug 20 00:57:21 UTC 2008


Now, as to what Hans has proposed regarding the encoder.  I
completely side with his idea of a streaming API.  Currently, the
only possible means of customization is to add methods to the
ENCODE-JSON generic function.  This is, obviously, not very good,
because (a) the GF eventually gets cluttered by multitude obscure
heterogenous methods; (b) it is not possible to have different
encoding strategies for one and the same data type; (c) the
library really adds no value, as the technicalities of JSON
syntax still have to be dealt with inside the new method.

I have to admit I don't quite understand Henrik's objections
against the proposal.  As far as I see, there is no conflict at
all between that and the current simple API, as the latter can be
absolutely gracefully reimplemented on top of the former.  E.g.,
instead of

(defmethod encode-json ((s sequence) stream)
   (with-sequence-iterator (generator s)
     (write-json-array generator stream)))

we could write something like

(defmethod encode-json ((s sequence) stream)
   (with-json-array (:stream stream)
     (map nil #'encode-array-element s)))

The user of the library is, of course, free to write his own
encoder specialized for his own classes, as nothing prevents him
from calling encode-json in his default method, e.g.:

(defmethod my-own-encode-json ((obj my-class) stream)
   (with-json-object (:stream stream)
     (with-object-element ("initial")
       (my-own-encode-json (get-value-for-initial-field obj)
                           stream))
     (with-object-element ("subsequent")
       (my-own-encode-json (get-value-for-subsequent-field obj)
                           stream))))

(defmethod my-own-encode-json ((value t) stream)
   (encode-json value stream))

CL-JSON can very well provide some syntactic sugar for objects,
along the lines of:

(defmacro with-json-object-for-fields ((field (&rest fields)
                                               &key stream)
                                        &body body)
   `(with-json-object (:stream stream)
      ,@(loop for (name getter) in fields
          collect `(with-object-element (,name)
                     (let ((,field ,getter)) , at body)))))

Which allows us to rewrite the method above as

(defmethod my-own-encode-json ((obj my-class) stream)
   (with-json-object-for-slots
       (slot (("initial" (get-value-for-initial-field obj))
              ("subsequent" (get-value-for-subsequent-field obj)))
        :stream stream)
     (my-own-encode-json slot stream)))

This kind of encoder transparency shall make for a natural and
useful counterpart to decoder customizability.

Sincerely,
  - B. Smilga.


P.S.  I was thinking about a customizable encoder myself, but
only came up with a very clumsy idea of exporting
WRITE-JSON-OBJECT and WRITE-JSON-ARRAY, and having their
GENERATOR-FN arguments return callbacks where customized encoding
is needed.  We would have

(defmethod encode-json ((fn function) stream)
   (funcall fn stream))

and then use that in the following manner:

(defmethod my-own-encode-json ((obj my-class) stream)
   (write-json-object (make-my-class-iterator obj) stream))

(defun make-my-class-iterator (obj)
   (let ((state :initial))
     (lambda ()
       (ecase state
          (:initial
           (setq state :subsequent)
           (values t :initial-field
                   (lambda (stream)      ; a callback
                     (my-own-encode-json
                      (get-value-for-initial-field obj)
                      stream))))
          (:subsequent
           (setq state :final)
           (values t :subsequent-field
                   (lambda (stream)      ; another one
                     (my-own-encode-json
                      (get-value-for-subsequent-field obj)
                      stream))))
          (:final nil)))))

Compared to Hans's approach, mine is just an insult to good taste
and common sense.




More information about the cl-json-devel mailing list