On 14 Aug 2011, at 16:24, Hraban Luyat wrote:
The semantics of my incoming messages are thus:
object:
- "type": string denoting the type
- "payload": type-specific payload
I want to create a decoder that only extracts the type and uses that to determine which decoder to send the payload to. Then it continues with whatever lisp object the decoder returned.
What I thought would be appropriate is to create a generic function;
(defgeneric json->data (type payload))
and then simply register decoders as follows:
(defmethod json->data ((type (eq :foo)) payload) "Decode message of type foo." ...)
(defmethod json->data ((type (eq :bar)) payload) "Decode message of type bar." ...)
But now I am not really sure how to glue this together. What would you recommend? Is this the right frame of mind at all or should I take a totally different approach?
Why, yes, you can certainly make it that way.
Re. gluing it together: there is some relevant reading in CL-JSON User Guide under http://common-lisp.net/project/cl-json/#DECODER- CUSTOMIZATION . To apply the API to your case, you'd have to define a dynamic variable which would store the type while the decoder is waiting for the payload to arrive, and another, which would store the payload while the decoder is waiting for the end of the object:
(defvar *payload-type* nil)
(defvar *payload* nil)
Then you customize your decoder to decode top-level JSON {} objects in the following way:
(let ((default-decoder (json:current-decoder))) (json:bind-custom-vars (;; Initialize variables in the dynamic scope of the object: :beginning-of-object (lambda () (setq *payload-type* nil *payload* nil)) ;; The handler for key sets the internal decoder and the ;; handler for value depending on the key: :object-key (lambda (json-id) (let ((lisp-id (json:safe-json-intern (funcall json:*json-identifier-name-to-lisp* json-id)))) (case lisp-id ;; If the key is "type", the value is decoded in the ;; standard way and stored in *payload-type*: ((:type) (json:set-custom-vars :internal-decoder default-decoder :object-value (lambda (value) (setq *payload-type* value)))) ;; If the key is "payload", the value is decoded by ;; your type-specific decoder and stored in *payload*: ((:payload) (json:set-custom-vars :internal-decoder (lambda (stream) (json->data *payload-type* stream)) :object-value (lambda (value) (setq *payload* value))))))) ;; The value of the whole object is the value decoded from ;; its "payload" field: :end-of-object (lambda () *payload*) ;; This you need only if your type+payload objects may be ;; nested: :object-scope '(*payload* *payload-type*)) (decode-json)))
For clarity, I've left out any safety checks, which you might want to put in here or there. Also, this code includes a tacit assumption that, in your objects, the "type" field always precedes the "payload" field. If you cannot guarantee this, you'll have to provide a json-
data method for type NIL which maybe decodes to some intermediate
representation, and add code to the value handler for "type" fields to finalize such semi-parsed values if found in *payload*.
Hope this helps; feel free to ask me for clarifications if you find some portion of the code to be too obscure.
Sincerely, - B. Smilga.