如何使用cl-json库将json-string转换为CLOS对象? [英] How to convert json-string into CLOS object using cl-json library?

查看:119
本文介绍了如何使用cl-json库将json-string转换为CLOS对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果有一个类和一个json:

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

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

如何使用cl-json库将*input*转换为foo的实例?

我想应该是这样的:

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

但是会产生:

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

我在做什么错了?

解决方案

错误的原因是cl-json:*json-symbols-package*绑定到了KEYWORD包:当JSON键变成符号时,它们变成了显然是关键字的关键字.无效作为插槽名称.

流体对象

以下作品:

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

(注意:您只需要在双引号字符前加反斜杠)

您获得一个FLUID-OBJECT.

JSON数据中的原型键

现在,您还可以定义自己的类:

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

然后,JSON需要具有"prototype"键:

 (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\"  }}")))

上面的代码返回FOO的一个实例.

您可以通过重新绑定*prototype-name*来使用与"prototype"不同的键.

强制使用默认原型(hack)

在不更改现有库代码的情况下,您可以对其进行修改以更改解码步骤的行为.该代码是围绕特殊变量组织的,这些特殊变量在解析的各个阶段用作回调,因此可以使用您自己的函数包装所需的函数:

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

上面的代码为给定的类(符号)创建了一个原型对象,捕获了*end-of-object-handler*的当前绑定,并返回一个闭包,该闭包在被调用时将*prototype*绑定到封闭的原型实例上. >

然后,您将其命名如下:

(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的实例.

递归

请注意,如果您按以下方式定义foo:

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

然后,该黑客还将嵌套的JSON对象读取为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>

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?

解决方案

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>

这篇关于如何使用cl-json库将json-string转换为CLOS对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆