我正在学习Common Lisp,并尝试将一些JSON发送到Web服务。我要发送的JSON以类似于以下内容的结构表示:
((:ITEMS
((:KEY . "value1") (:IGNORE 1 2 3))
((:KEY . "value2") (:IGNORE 1 2 3))))
我想从:IGNORE
中的每个项目中删除密钥:ITEMS
。
在Clojure中(我更熟悉)我会使用update
将功能更改应用于键:ITEMS
,映射每个项目,然后使用select-keys
或dissoc
获得我需要的结构。但是,我无法找到在Common Lisp中执行此修改的正确方法。
我应该使用copy-tree
复制结构,然后使用setf
更改树吗?
NB1:我不确定我使用的数据结构是否“正常”,但这是我从CL-Json包调用json:decode-json-from-string
时返回的结果。
到目前为止,我有这个
(SETF (CDR (ASSOC :ITEMS JSON))
(REMOVE-IF (LAMBDA (E) (EQUAL (CAR E) :IGNORE)) (CDR (ASSOC :ITEMS JSON))))
这是错误的(E
是一个((:KEY . "value1") (:IGNORE 1 2 3))
形式的列表,但我看不出如何正确使用我的代码。
NB2:这就是我在Clojure中的表现:
(update json :items #(map (fn [x] (dissoc x :ignore)) %))
答案 0 :(得分:4)
其他答案已经显示了使用alists执行此操作的方法,但您也可以自定义json解码器以返回更方便的数据结构。由于您希望在功能上/持久性地使用它们,FSet库可能是一个不错的选择。
(ql:quickload '(cl-json :fset :alexandria))
(use-package :alexandria)
首先,让我们自定义解码器,将json对象转换为FSET:MAP
,将json数组转换为FSET:SEQ
。
(defvar *current-array* nil)
(defun beginning-of-array ()
(setf *current-array* (fset:empty-seq)))
(defun array-member (item)
(fset:push-last *current-array* item))
(defun end-of-array ()
*current-array*)
(defvar *current-object* nil)
(defvar *current-object-key* nil)
(defun beginning-of-object ()
(setf *current-object* (fset:empty-map)))
(defun object-key (key)
(setf *current-object-key*
(funcall json:*identifier-name-to-key*
(funcall json:*json-identifier-name-to-lisp* key))))
(defun object-value (value)
(fset:includef *current-object* *current-object-key* value))
(defun end-of-object ()
*current-object*)
(defun call-with-fset-decoder-semantics (function)
(let ((json:*aggregate-scope-variables*
(list* '*current-array* '*current-object* '*current-object-key*
json:*aggregate-scope-variables*)))
(json:bind-custom-vars (:beginning-of-array #'beginning-of-array
:array-member #'array-member
:end-of-array #'end-of-array
:beginning-of-object #'beginning-of-object
:object-key #'object-key
:object-value #'object-value
:end-of-object #'end-of-object)
(funcall function))))
(defmacro with-fset-decoder-semantics (&body body)
`(call-with-fset-decoder-semantics (lambda () ,@body)))
假设您的JSON看起来像这样。
(defvar *json* "{
\"something\": \"foo\",
\"items\": [
{
\"key\": \"value1\",
\"ignore\": [1, 2, 3]
},
{
\"key\": \"value2\",
\"ignore\": [1, 2, 3]
}
],
\"other\": \"bar\"
}")
现在您可以将其解码为FSet结构。请注意,FSet使用其自定义阅读器语法来打印对象。 #{| (key value)* |}
表示FSET:MAP
,#[ ... ]
表示FSET:SEQ
。如果您希望能够阅读它们,可以使用FSET:FSET-SETUP-READTABLE
,但这不是必需的。您应该在执行此操作之前创建自定义可读表。
(with-fset-decoder-semantics
(json:decode-json-from-string *json*))
;=> #{|
; (:ITEMS
; #[
; #{| (:KEY "value1") (:IGNORE #[ 1 2 3 ]) |}
; #{| (:KEY "value2") (:IGNORE #[ 1 2 3 ]) |} ])
; (:OTHER "bar")
; (:SOMETHING "foo") |}
您可以使用FSET:IMAGE
从:IGNORE
的项中删除FSET:LESS
项。 FSET:WITH
会为地图添加一个键,或替换现有键。
(let ((data (with-fset-decoder-semantics
(json:decode-json-from-string *json*))))
(fset:with data :items (fset:image (rcurry #'fset:less :ignore)
(fset:lookup data :items))))
;=> #{|
; (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
; (:OTHER "bar")
; (:SOMETHING "foo") |}
FSet还可以方便地处理修改宏:
(let ((data (with-fset-decoder-semantics
(json:decode-json-from-string *json*))))
(fset:imagef (fset:lookup data :items) (rcurry #'fset:less :ignore))
data)
;=> #{|
; (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
; (:OTHER "bar")
; (:SOMETHING "foo") |}
这可能看起来好像是破坏性地修改数据,但实际上并非如此。
(let* ((data (with-fset-decoder-semantics
(json:decode-json-from-string *json*)))
(old-data data))
(fset:imagef (fset:lookup data :items) (rcurry #'fset:less :ignore))
(values data old-data))
;=> #{|
; (:ITEMS #[ #{| (:KEY "value1") |} #{| (:KEY "value2") |} ])
; (:OTHER "bar")
; (:SOMETHING "foo") |}
;=> #{|
; (:ITEMS
; #[
; #{| (:KEY "value1") (:IGNORE #[ 1 2 3 ]) |}
; #{| (:KEY "value2") (:IGNORE #[ 1 2 3 ]) |} ])
; (:OTHER "bar")
; (:SOMETHING "foo") |}
答案 1 :(得分:2)
快速拍摄,以下是Clojure update
端口的不完整尝试:
(defun update (a-list keyword fun)
"Creates a new a-list from the given a-list where the item under the given
keyword is replaced by the value created by applying fun to it. Expects the
a-list to have only keywords as keys and those keywords to be unique."
(loop :for el :in a-list
:for (a . d) := el
:if (eq a keyword)
:collect (cons a (funcall fun d))
:else
:collect el))
在您的情况下,它可以像这样使用:
(update '((:ITEMS ((:KEY . "value1") (:IGNORE 1 2 3))
((:KEY . "value2") (:IGNORE 1 2 3))))
:items
(lambda (items)
(mapcar (lambda (item)
(remove :ignore item :key #'car))
items)))
返回:
((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))
(这应该稍微改进一下,例如,一旦找到密钥就可以快捷方式,允许其他密钥,参数化常用密钥和测试功能等。)
答案 2 :(得分:2)
执行此操作的基本非破坏性方法是:
(let ((data '((:ITEMS
((:KEY . "value1") (:IGNORE 1 2 3))
((:KEY . "value2") (:IGNORE 1 2 3))))))
(acons :items
(mapcar (lambda (alist)
(remove :ignore alist :key #'car))
(cdr (assoc :items data)))
(remove :items data :key #'car)))
=> ((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))
最后一次拨打"删除"并非绝对必要;如果你离开"数据"相反,您将在结果列表中保留先前:items
的副本。是否要这样做取决于您的需求。
(defun arem (key &optional (alist nil alistp))
(if alistp
#1=(remove key alist :key #'car)
(lambda (alist) #1#)))
(defun update-fn (fn key &optional (alist nil alistp))
(if alistp
#1=(acons key (funcall fn (cdr (assoc key alist))) (arem key alist))
(lambda (alist) #1#)))
现在看起来像这样:
(update-fn (lambda (u) (mapcar (arem :ignore) u))
:items
'((:ITEMS
((:KEY . "value1") (:IGNORE 1 2 3))
((:KEY . "value2") (:IGNORE 1 2 3)))))
最后:
(defmacro each (expr &aux (list (gensym)))
`(lambda (,list)
(mapcar ,expr ,list)))
给出了:
(update-fn (each (arem :ignore))
:items
'((:ITEMS
((:KEY . "value1") (:IGNORE 1 2 3))
((:KEY . "value2") (:IGNORE 1 2 3)))))
=> ((:ITEMS ((:KEY . "value1")) ((:KEY . "value2"))))
你也可以写:
(update-fn (each (arem :ignore)) :items)
......你获得了一个封闭。