Как преобразовать json-строку в объект CLOS, используя библиотеку cl-json? - PullRequest
2 голосов
/ 06 марта 2019

Если есть класс и JSON:

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

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

Как преобразовать *input* в экземпляр foo, используя библиотеку cl-json?

Я думаю, это должно быть что-то вроде:

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

Что я делаю не так?

1 Ответ

3 голосов
/ 06 марта 2019

Причина ошибки заключается в том, что 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", связав *prototype-name*.

Принудительный прототип по умолчанию (хак)

Не изменяя существующий код библиотеки, вы можете взломать его, чтобы изменить поведение шага декодирования. Код организован вокруг специальных переменных, которые используются в качестве обратных вызовов в различных точках синтаксического анализа, поэтому необходимо обернуть ожидаемую функцию своей собственной:

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

...