How to convert json-string into CLOS object using cl-json library?

陌路散爱 提交于 2019-12-13 16:17:20

问题


If there is a class and a json:

(defclass foo ()
    ((bar :initarg :bar)))

(defvar *input* "\{ \"bar\" : 3 }")

How to convert *input* into an instance of foo using cl-json library?

I guess it should be something like:

(with-decoder-simple-clos-semantics
    (let ((*prototype-name* 'foo))
      (decode-json-from-string *input*)))

But it produces:

Invalid SB-MOP:SLOT-DEFINITION initialization: the
initialization argument :NAME was constant: :BAR.
   [Condition of type SB-PCL::SLOTD-INITIALIZATION-ERROR]

What am I doing wrong?


回答1:


The cause of the error is that cl-json:*json-symbols-package* is bound to the KEYWORD package: when JSON keys are turned into symbols, they become keywords which apparently are not valid as slot names.

Fluid objects

The following works:

(let ((json:*json-symbols-package* (find-package :cl-user)))
  (json:with-decoder-simple-clos-semantics
    (json:decode-json-from-string "{ \"bar\" : 3 }")))

(note: you only need backslashes before double-quote characters)

You obtain a FLUID-OBJECT.

Prototype key in JSON data

Now, you can also define your own class:

(in-package :cl-user)
(defclass foo ()
  ((bar :initarg :bar)))

And then, the JSON needs to have a "prototype" key:

 (let ((json:*json-symbols-package* (find-package :cl-user)))
   (json:with-decoder-simple-clos-semantics
     (json:decode-json-from-string
      "{ \"bar\" : 3 , 
         \"prototype\" : { \"lispClass\" : \"foo\", 
                           \"lispPackage\" : \"cl-user\"  }}")))

The above returns an instance of FOO.

You can use a different key than "prototype" by rebinding *prototype-name*.

Force a default prototype (hack)

Without changing the existing library code, you can hack around it to change the behavior of the decoding step. The code is organized around special variables that are used as callbacks at various point of the parsing, so it is a matter of wrapping the expected function with your own:

(defun wrap-for-class (class &optional (fn json::*end-of-object-handler*))
  (let ((prototype (make-instance 'json::prototype :lisp-class class)))
    (lambda ()
      ;; dynamically rebind *prototype* right around calling fn
      (let ((json::*prototype* prototype))
        (funcall fn)))))

The above creates a prototype object for the given class (symbol), capture the current binding of *end-of-object-handler*, and returns a closure that, when called, bind *prototype* to the closed-over prototype instance.

Then, you call it as follows:

(let ((json:*json-symbols-package* *package*))
  (json:with-decoder-simple-clos-semantics
    (let ((json::*end-of-object-handler* (wrap-for-class 'foo)))
      (json:decode-json-from-string
       "{ \"bar\" : 3 }"))))

And you have an instance of FOO.

Recursion

Note that if you define foo as follows:

(defclass foo ()
  ((bar :initarg :bar :accessor bar)
   (foo :initarg :foo :accessor foo)))

Then the hack also reads nested JSON objects as FOO:

(let ((json:*json-symbols-package* *package*))
  (json:with-decoder-simple-clos-semantics
    (let ((json::*end-of-object-handler* (wrap-for-class 'foo)))
      (json:decode-json-from-string
       "{ \"bar\" : 3, \"foo\" : { \"bar\" : 10} }"))))

=> #<FOO {1007A70E23}>

> (describe *)
#<FOO {1007A70E23}>
  [standard-object]

Slots with :INSTANCE allocation:
  BAR                            = 3
  FOO                            = #<FOO {1007A70D53}>

> (describe (foo **))
#<FOO {1007A70D53}>
  [standard-object]

 Slots with :INSTANCE allocation:
  BAR                            = 10
  FOO                            = #<unbound slot>


来源:https://stackoverflow.com/questions/55015871/how-to-convert-json-string-into-clos-object-using-cl-json-library

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!