cl-defstruct 定义出来的不能使用 json-encode 吗

如题, 在使用 emacs-jupyter 的时候发现了一个报错, 调试了半天发现是代码里有处涉及到 json-encode 一个 cl-defstruct 出来的值, 会报错

简单的尝试了一下

(cl-defstruct foo (f1 “foo”)) (json-encode (make-foo))

发现确实不行, 报错,

(json-error #s(foo :f1 “foo”))

这是因为 json.el 确实缺少了相关支持吗, 还是我使用上哪里有问题

  1. 第一时间应该看文档。

    文档明确了 json-encode 接受的对象:

    (json-encode OBJECT)
    
    Return a JSON representation of OBJECT as a string.
    
    OBJECT should have a structure like one returned by ‘json-read’.
    
    (json-read)
    
    Parse and return the JSON object following point.
    Advances point just past JSON object.
    
    If called with the following JSON after point
    
      {"a": [1, 2, {"c": false}],
       "b": "foo"}
    
    you will get the following structure returned:
    
      ((a .
          [1 2
             ((c . :json-false))])
       (b . "foo"))
    
  2. 帖子里的代码应该放在 ```{lang}```

  3. 安装 xuchunyang 大佬的 elisp-demos,然后你在看文档的同时也能看到使用范例。

现在 29 添加了 shortdoc,两个都放到 help 中会重复吗

可以看看函数 json--print ,里面有支持的类别:

(defun json--print (object)
  "Like `json-encode', but insert or print the JSON at point."
  (cond ((json--print-keyword object))
        ((listp object)         (json--print-list object))
        ((json--print-stringlike object))
        ((numberp object)       (prin1 object))
        ((arrayp object)        (json--print-array object))
        ((hash-table-p object)  (json--print-unordered-map object))
        ((signal 'json-error (list object)))))

感谢大家的回答, 我更震惊的是 json.el 出于什么样的理由不支持 record 呢, 感觉难以置信

我猜可能是这样,record/struct 需要知道它的结构才有意义,而它的实例只保留了各个字段的 value,所以没法转成 key/value 形式。

#+BEGIN_SRC elisp
(cl-defstruct (person (:constructor person-create)
                      (:copier nil))
  name age sex)

(person-create :name "Tom" :age 20 :sex "Male")
#+END_SRC

#+RESULTS:
: #s(person "Tom" 20 "Male")

当然要转也不是不可能,只要数据能读取就能转:

  1. 转成key/value 形式,

    给 json-encode 增加一个字段描述的参数:

     (json-encode tom (cl-struct-slot-info 'person))
    

    或者实现一个 struct-to-alist 还更靠谱:

     (json-encode (struct-to-alist tom 'person)
    
  2. 当作 list 对待。先把 record 转成 list,然后转 json。

  1. record-to-list
(defun record-to-list (record)
  "Convert RECORD to list."
  (with-temp-buffer
    (prin1 record (current-buffer))
    (goto-char (point-min))
    (when (and (re-search-forward "#s" nil t) (= (char-after) ?\())
      (read (current-buffer)))))

(record-to-list (person-create :name "Tom" :age 20 :sex "Male"))
;; => (person "Tom" 20 "Male")
  1. struct-to-alist
(defun struct-to-alist (instance)
  "Convert struct to alist."
  (let ((lst (record-to-list instance)))
    (cl-loop for slot in (cdr (cl-struct-slot-info (car lst)))
             for value in (cdr lst)
             collect (cons (car slot) value))))

(struct-to-alist (person-create :name "Tom" :age 20 :sex "Male"))
;; => ((name . "Tom") (age . 20) (sex . "Male"))

EDIT: 去掉 struct-to-aliststruct-type 参数。

3 个赞

我糊涂了。根本没必要破坏 json-encode 接口,struct 对象本身就包含 struct-type:

(person-create :name "Tom" :age 20 :sex "Male")
;; => #s(person "Tom" 20 "Male")
;;       ^^^^^^

所以只要修改 json--print,然后实现 json--print-structjson--print-record 就可以了:

+ (defun json--print-struct (object) ...)
+ (defun json--print-redord (object) ...)

(defun json--print (object)
  "Like `json-encode', but insert or print the JSON at point."
  (cond ((json--print-keyword object))
        ((listp object)         (json--print-list object))
        ((json--print-stringlike object))
        ((numberp object)       (prin1 object))
        ((arrayp object)        (json--print-array object))
        ((hash-table-p object)  (json--print-unordered-map object))
+       ((cl-struct-p object)   (json--print-struct object))
+       ((and (recordp object) (not (cl-struct-p object))) (json--print-record object))
        ((signal 'json-error (list object)))))

当然 #7 楼的两个转换函数已经够用了,不修改 json.el 亦无大碍。

1 个赞