在 Common Lisp 中访问 Assoc 列表的更好方法
Better way to access an Assoc list in Common Lisp
我有一个正在使用的天气获取应用程序,但我在使用关联列表时遇到了一些麻烦。我使用 openweathermap 和 convert-to-json:
从我的 get-weather 函数返回了以下列表
((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100)) (:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331) (:NAME . "Vancouver") (:COD . 200))
我正在尝试访问 :weather :main rain。目前我在做:
(cdr (second (second (assoc :weather *assoc-list-from-above*))))
有没有更好的方法?
如果您经常使用这些值,您可能希望将列表转换为 CLOS 对象。 json-library 可能有一些东西可以帮助您做到这一点,但这里有一个手动完成天气部分的示例。您可以类似地完成其余部分。
(defparameter *data*
'((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100)) (:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331) (:NAME . "Vancouver") (:COD . 200)))
;; Define classes for the parts that you're interested in.
;; You can leave out the slots you don't need.
(defclass weather ()
((id :initarg :id :reader id)
(main :initarg :main :reader main)
(description :initarg :description :reader description)
(icon :initarg :icon :reader icon)))
;; This just decodes the :weather part.
(defun decode-weather (alist)
(make-instance 'weather
:id (cdr (assoc :id alist))
:main (cdr (assoc :main alist))
:description (cdr (assoc :description alist))
:icon (cdr (assoc :icon alist))))
(defparameter *weather* (decode-weather (second (assoc :weather *data*))))
(id *weather*) ; => 500
(main *weather*) ; => "Rain"
(description *weather*) ; => "light rain"
(icon *weather*) ; => "10n"
编辑: 添加其他选项:您可以查看 json-bind
实用程序。
(defparameter *json* "{\"foo\": {\"bar\": 5, \"quux\": 10, \"foobar\":[1, 2, 3]}}")
(json:json-bind (foo.bar
foo.quux
foo.foobar)
*json*
(format t "bar: ~a~%quux: ~a~%foobar: ~a~%"
foo.bar foo.quux foo.foobar))
;; output:
;; bar: 5
;; quux: 10
;; foobar: (1 2 3)
借用 *input*
来自约书亚:
(defparameter *input*
"{\"coord\":{\"lon\":-123.12,\"lat\":49.25},\"weather\":{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"},\"base\":\"cmc stations\",\"main\":{\"temp\":281.56,\"pressure\":1001,\"humidity\":93,\"temp--min\":276.15,\"temp--max\":283.15},\"wind\":{\"speed\":3.1,\"deg\":100,\"clouds\":{\"all\":90}},\"dt\":1453467600,\"sys\":{\"sunrise\":1453478139,\"sunset\":1453510389},\"id\":6173331,\"name\":\"Vancouver\",\"cod\":200}")
(json:json-bind (weather.main
sys.sunrise) *input*
(format t "Weather main: ~a~%Sys surise: ~a~%" weather.main sys.sunrise))
另一个编辑: 如果您有多个 weather
,您最好的选择是使用 CLOS(我的选择或 Joshuas)。如果你还需要使用天气以外的其他字段,你可以结合我给出的两个解决方案:
(json:json-bind (weather sys.sunrise) *input*
(format t "sys sunrise: ~a~%" sys.sunrise)
(loop
for w in weather
for dw = (decode-weather w)
do (format t "Weather: ~a, ~a~%" (main dw) (description dw))))
如果您不想使用 CLOS,您也可以这样做:
(defun decode-weather-2 (alist)
(list (cdr (assoc :main alist))
(cdr (assoc :description alist))))
(json:json-bind (weather sys.sunrise) *input*
(format t "sys sunrise: ~a~%" sys.sunrise)
(loop
for w in weather
for (main description) = (decode-weather-2 w)
do (format t "Weather: ~a, ~a~%" main description)))
首先,使用一些中间变量使您对关联列表的访问更清晰一些就足够了。首先,让我们定义一个我们可以解析的输入字符串(以后请尝试在问题中提供这些,因为它们将帮助其他人提供答案):
(defparameter *input*
"{\"coord\":{\"lon\":-123.12,\"lat\":49.25},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"base\":\"cmc stations\",\"main\":{\"temp\":281.56,\"pressure\":1001,\"humidity\":93,\"temp--min\":276.15,\"temp--max\":283.15},\"wind\":{\"speed\":3.1,\"deg\":100,\"clouds\":{\"all\":90}},\"dt\":1453467600,\"sys\":{\"sunrise\":1453478139,\"sunset\":1453510389},\"id\":6173331,\"name\":\"Vancouver\",\"cod\":200}")
现在,在我看来,您可以通过以下方式更清晰地提取天气字段的主字段值:
(let* ((report (cl-json:decode-json-from-string *input*))
(weather (first (cdr (assoc :weather report))))
(main (cdr (assoc :main weather))))
main)
;;=> "Rain"
如果您要像这样重复调用 (cdr (assoc …)),那么基于提供的路径执行此操作的函数将大有帮助,如所示。当然,数组索引(例如,您想要天气列表的第一个元素)会使事情变得不那么干净。
现在,您还可以解码为 CLOS 实例。 CL-JSON 可以将 JSON 解码为匿名 CLOS classes 的实例。单独这样做会稍微改变访问权限,但不会改变太多。尽管如此,它还是让现场访问它的工作方式变得更加清晰。请注意,由于天气值现在是一个数组,而不是列表,我们得到的第一个元素是 (aref array 0),而不是 (first list).
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let* ((report (json:decode-json-from-string *input*))
(weather (aref (slot-value report 'weather) 0))
(main (slot-value weather 'main)))
main)))
;;=> "Rain"
现在,我认为使用 CLOS classes 的真正好处是您可以定义自己的,然后使用 change-class 将 CL-JSON 给您的实例更改为你自己的实例 class。
在代码中定义 class 也对文档有很大帮助。对于一个小示例来说,这似乎没什么大不了的,但在编写可维护的代码时,这非常重要。例如,我们现在可以记录这些插槽的预期类型及其含义。这是一个可行的 class 定义。请注意,人们对命名约定有不同的看法(例如,是否使用 wreport-weather 作为访问器或 weather)。
(defclass wreport ()
((coord
:accessor wreport-coord
:documentation "An object with LON and LAT slots.")
(weather
:accessor wreport-weather
:documentation "An array of objects with ID, MAIN, DESCRIPTION, and ICON slots.")
(base) ;; and so on ...
(main)
(wind)
(dy)
(sys)
(id)
(name)))
现在你可以使用change-class将你的对象变成wreport,然后你可以使用wreport-weather(连同aref,因为值仍然是一个数组)得到子对象,然后你可以使用 slot-value(同上),得到主字段:
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let ((x (json:decode-json-from-string *input*)))
(let* ((wreport (change-class x 'wreport))
(weather (aref (wreport-weather wreport) 0))
(main (slot-value weather 'main)))
main))))
;;=> "Rain"
为天气元素定义一个 subclass 可能是有意义的,这并不难。由于我们调用了顶层事物 wreport,我们可以调用较低层事物 subreports:
(defclass subreport ()
((id
:accessor subreport-id
:documentation "")
(main
:accessor subreport-main
:documentation "A short string containing a concise description of the weather.")
(description
:accessor subreport-description
:documentation "...")
(icon
:accessor subreport-icon
:documentation "...")))
现在,唯一剩下要做的就是在我们使用 change-class 将顶级报告更改为 wreport[ 的实例之后=62=],我们需要为天气数组中的每个元素调用change-class,将其变成一个子报表。根据change-class的文档,update-instance-for-different-class被调用。我们可以定义一个 :after 方法来为我们做转换:
(defmethod update-instance-for-different-class :after (previous (current wreport) &rest initargs &key &allow-other-keys)
"When changing an instance of something into a WREPORT, recursively
change the elements of the WEATHER array (if bound) to elements of
SUBREPORT."
(declare (ignore initargs))
(when (slot-boundp current 'weather)
(loop for sub across (wreport-weather current)
do (change-class sub 'subreport))))
如果您没有对 CLOS 做太多,那可能有点吓人,但您实际上是在说“在 change-class 完成所有工作之后, 还要再做一次转换。现在你可以在两个级别上使用域适当的访问器:
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let ((x (json:decode-json-from-string *input*)))
(let* ((wreport (change-class x 'wreport))
(subreport (aref (wreport-weather wreport) 0))
(main (subreport-main subreport))) ;; (slot-value subreport 'main)))
main))))
;;=> "Rain"
这看起来似乎有很多工作要做,但对于一个快速的小脚本来说,它可能确实如此。但是,如果您需要这些结构一段时间,将文档编写到代码中将会很有帮助。如果你需要构建任何天气预报,拥有一个好的领域模型会有很大帮助。
您可以编写一个函数,在其中传递访问路径:
(defun get-data (list attributes)
(flet ((get-it (attribute)
(if (listp attribute)
(destructuring-bind (key extractor) attribute
(funcall extractor (cdr (assoc key list))))
(cdr (assoc attribute list)))))
(if (cdr attributes)
(get-data-list (get-it (first attributes)) (rest attributes))
(get-it (first attributes)))))
路径中的元素可以是键或(键提取器)的列表。提取器需要是一个从返回的关联列表项中提取数据的函数。
(defparameter *data*
'((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100))
(:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331)
(:NAME . "Vancouver")
(:COD . 200)))
示例:
CL-USER 22 > (get-data *data* '((:weather first) :main))
"Rain"
CL-USER 23 > (get-data *data* '((:weather first) :icon))
"10n"
CL-USER 24 > (get-data *data* '(:main :temp))
281.56
任务:
- 使其更健壮等
- 处理不同的列表:关联列表、属性 列表、包含项目的列表...
虽然我真的很喜欢@jkiiski 的 json-bind
解决方案,但我想我也会添加以下选项。
如果查询路径在 compile-time 处已知,您可以使用以下宏
(defmacro report-get (report &optional key &rest keys)
(cond
((null key) report)
((integerp key) `(report-get (nth ,key ,report) ,@keys))
(t `(report-get (cdr (assoc ,key ,report)) ,@keys))))
示例:
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Rain"
CL-USER> (report-get *array-from-above* :coord :lon)
-123.12
CL-USER> (macroexpand '(report-get *array-from-above* :weather 0 :main))
(CDR (ASSOC :MAIN (NTH 0 (CDR (ASSOC :WEATHER *ARRAY-FROM-ABOVE*)))))
T
(report-get *array-from-above* :weather 0 :main)
中的0
是访问天气项集合中的第一项
编辑:忘了说 - 这个宏是 setf
可用的。
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Rain"
CL-USER> (setf (report-get *array-from-above* :weather 0 :main) "Sunny")
"Sunny"
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Sunny"
可能对您的要求没有用,但很高兴知道。
我有一个正在使用的天气获取应用程序,但我在使用关联列表时遇到了一些麻烦。我使用 openweathermap 和 convert-to-json:
从我的 get-weather 函数返回了以下列表((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100)) (:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331) (:NAME . "Vancouver") (:COD . 200))
我正在尝试访问 :weather :main rain。目前我在做:
(cdr (second (second (assoc :weather *assoc-list-from-above*))))
有没有更好的方法?
如果您经常使用这些值,您可能希望将列表转换为 CLOS 对象。 json-library 可能有一些东西可以帮助您做到这一点,但这里有一个手动完成天气部分的示例。您可以类似地完成其余部分。
(defparameter *data*
'((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100)) (:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331) (:NAME . "Vancouver") (:COD . 200)))
;; Define classes for the parts that you're interested in.
;; You can leave out the slots you don't need.
(defclass weather ()
((id :initarg :id :reader id)
(main :initarg :main :reader main)
(description :initarg :description :reader description)
(icon :initarg :icon :reader icon)))
;; This just decodes the :weather part.
(defun decode-weather (alist)
(make-instance 'weather
:id (cdr (assoc :id alist))
:main (cdr (assoc :main alist))
:description (cdr (assoc :description alist))
:icon (cdr (assoc :icon alist))))
(defparameter *weather* (decode-weather (second (assoc :weather *data*))))
(id *weather*) ; => 500
(main *weather*) ; => "Rain"
(description *weather*) ; => "light rain"
(icon *weather*) ; => "10n"
编辑: 添加其他选项:您可以查看 json-bind
实用程序。
(defparameter *json* "{\"foo\": {\"bar\": 5, \"quux\": 10, \"foobar\":[1, 2, 3]}}")
(json:json-bind (foo.bar
foo.quux
foo.foobar)
*json*
(format t "bar: ~a~%quux: ~a~%foobar: ~a~%"
foo.bar foo.quux foo.foobar))
;; output:
;; bar: 5
;; quux: 10
;; foobar: (1 2 3)
借用 *input*
来自约书亚:
(defparameter *input*
"{\"coord\":{\"lon\":-123.12,\"lat\":49.25},\"weather\":{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"},\"base\":\"cmc stations\",\"main\":{\"temp\":281.56,\"pressure\":1001,\"humidity\":93,\"temp--min\":276.15,\"temp--max\":283.15},\"wind\":{\"speed\":3.1,\"deg\":100,\"clouds\":{\"all\":90}},\"dt\":1453467600,\"sys\":{\"sunrise\":1453478139,\"sunset\":1453510389},\"id\":6173331,\"name\":\"Vancouver\",\"cod\":200}")
(json:json-bind (weather.main
sys.sunrise) *input*
(format t "Weather main: ~a~%Sys surise: ~a~%" weather.main sys.sunrise))
另一个编辑: 如果您有多个 weather
,您最好的选择是使用 CLOS(我的选择或 Joshuas)。如果你还需要使用天气以外的其他字段,你可以结合我给出的两个解决方案:
(json:json-bind (weather sys.sunrise) *input*
(format t "sys sunrise: ~a~%" sys.sunrise)
(loop
for w in weather
for dw = (decode-weather w)
do (format t "Weather: ~a, ~a~%" (main dw) (description dw))))
如果您不想使用 CLOS,您也可以这样做:
(defun decode-weather-2 (alist)
(list (cdr (assoc :main alist))
(cdr (assoc :description alist))))
(json:json-bind (weather sys.sunrise) *input*
(format t "sys sunrise: ~a~%" sys.sunrise)
(loop
for w in weather
for (main description) = (decode-weather-2 w)
do (format t "Weather: ~a, ~a~%" main description)))
首先,使用一些中间变量使您对关联列表的访问更清晰一些就足够了。首先,让我们定义一个我们可以解析的输入字符串(以后请尝试在问题中提供这些,因为它们将帮助其他人提供答案):
(defparameter *input*
"{\"coord\":{\"lon\":-123.12,\"lat\":49.25},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"base\":\"cmc stations\",\"main\":{\"temp\":281.56,\"pressure\":1001,\"humidity\":93,\"temp--min\":276.15,\"temp--max\":283.15},\"wind\":{\"speed\":3.1,\"deg\":100,\"clouds\":{\"all\":90}},\"dt\":1453467600,\"sys\":{\"sunrise\":1453478139,\"sunset\":1453510389},\"id\":6173331,\"name\":\"Vancouver\",\"cod\":200}")
现在,在我看来,您可以通过以下方式更清晰地提取天气字段的主字段值:
(let* ((report (cl-json:decode-json-from-string *input*))
(weather (first (cdr (assoc :weather report))))
(main (cdr (assoc :main weather))))
main)
;;=> "Rain"
如果您要像这样重复调用 (cdr (assoc …)),那么基于提供的路径执行此操作的函数将大有帮助,如
现在,您还可以解码为 CLOS 实例。 CL-JSON 可以将 JSON 解码为匿名 CLOS classes 的实例。单独这样做会稍微改变访问权限,但不会改变太多。尽管如此,它还是让现场访问它的工作方式变得更加清晰。请注意,由于天气值现在是一个数组,而不是列表,我们得到的第一个元素是 (aref array 0),而不是 (first list).
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let* ((report (json:decode-json-from-string *input*))
(weather (aref (slot-value report 'weather) 0))
(main (slot-value weather 'main)))
main)))
;;=> "Rain"
现在,我认为使用 CLOS classes 的真正好处是您可以定义自己的,然后使用 change-class 将 CL-JSON 给您的实例更改为你自己的实例 class。 在代码中定义 class 也对文档有很大帮助。对于一个小示例来说,这似乎没什么大不了的,但在编写可维护的代码时,这非常重要。例如,我们现在可以记录这些插槽的预期类型及其含义。这是一个可行的 class 定义。请注意,人们对命名约定有不同的看法(例如,是否使用 wreport-weather 作为访问器或 weather)。
(defclass wreport ()
((coord
:accessor wreport-coord
:documentation "An object with LON and LAT slots.")
(weather
:accessor wreport-weather
:documentation "An array of objects with ID, MAIN, DESCRIPTION, and ICON slots.")
(base) ;; and so on ...
(main)
(wind)
(dy)
(sys)
(id)
(name)))
现在你可以使用change-class将你的对象变成wreport,然后你可以使用wreport-weather(连同aref,因为值仍然是一个数组)得到子对象,然后你可以使用 slot-value(同上),得到主字段:
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let ((x (json:decode-json-from-string *input*)))
(let* ((wreport (change-class x 'wreport))
(weather (aref (wreport-weather wreport) 0))
(main (slot-value weather 'main)))
main))))
;;=> "Rain"
为天气元素定义一个 subclass 可能是有意义的,这并不难。由于我们调用了顶层事物 wreport,我们可以调用较低层事物 subreports:
(defclass subreport ()
((id
:accessor subreport-id
:documentation "")
(main
:accessor subreport-main
:documentation "A short string containing a concise description of the weather.")
(description
:accessor subreport-description
:documentation "...")
(icon
:accessor subreport-icon
:documentation "...")))
现在,唯一剩下要做的就是在我们使用 change-class 将顶级报告更改为 wreport[ 的实例之后=62=],我们需要为天气数组中的每个元素调用change-class,将其变成一个子报表。根据change-class的文档,update-instance-for-different-class被调用。我们可以定义一个 :after 方法来为我们做转换:
(defmethod update-instance-for-different-class :after (previous (current wreport) &rest initargs &key &allow-other-keys)
"When changing an instance of something into a WREPORT, recursively
change the elements of the WEATHER array (if bound) to elements of
SUBREPORT."
(declare (ignore initargs))
(when (slot-boundp current 'weather)
(loop for sub across (wreport-weather current)
do (change-class sub 'subreport))))
如果您没有对 CLOS 做太多,那可能有点吓人,但您实际上是在说“在 change-class 完成所有工作之后, 还要再做一次转换。现在你可以在两个级别上使用域适当的访问器:
(json:with-decoder-simple-clos-semantics
(let ((json:*json-symbols-package* nil))
(let ((x (json:decode-json-from-string *input*)))
(let* ((wreport (change-class x 'wreport))
(subreport (aref (wreport-weather wreport) 0))
(main (subreport-main subreport))) ;; (slot-value subreport 'main)))
main))))
;;=> "Rain"
这看起来似乎有很多工作要做,但对于一个快速的小脚本来说,它可能确实如此。但是,如果您需要这些结构一段时间,将文档编写到代码中将会很有帮助。如果你需要构建任何天气预报,拥有一个好的领域模型会有很大帮助。
您可以编写一个函数,在其中传递访问路径:
(defun get-data (list attributes)
(flet ((get-it (attribute)
(if (listp attribute)
(destructuring-bind (key extractor) attribute
(funcall extractor (cdr (assoc key list))))
(cdr (assoc attribute list)))))
(if (cdr attributes)
(get-data-list (get-it (first attributes)) (rest attributes))
(get-it (first attributes)))))
路径中的元素可以是键或(键提取器)的列表。提取器需要是一个从返回的关联列表项中提取数据的函数。
(defparameter *data*
'((:COORD (:LON . -123.12) (:LAT . 49.25))
(:WEATHER
((:ID . 500) (:MAIN . "Rain") (:DESCRIPTION . "light rain") (:ICON . "10n")))
(:BASE . "cmc stations")
(:MAIN (:TEMP . 281.56) (:PRESSURE . 1001) (:HUMIDITY . 93)
(:TEMP--MIN . 276.15) (:TEMP--MAX . 283.15))
(:WIND (:SPEED . 3.1) (:DEG . 100))
(:CLOUDS (:ALL . 90)) (:DT . 1453467600)
(:SYS (:TYPE . 1) (:ID . 3359) (:MESSAGE . 0.0039) (:COUNTRY . "CA")
(:SUNRISE . 1453478139) (:SUNSET . 1453510389))
(:ID . 6173331)
(:NAME . "Vancouver")
(:COD . 200)))
示例:
CL-USER 22 > (get-data *data* '((:weather first) :main))
"Rain"
CL-USER 23 > (get-data *data* '((:weather first) :icon))
"10n"
CL-USER 24 > (get-data *data* '(:main :temp))
281.56
任务:
- 使其更健壮等
- 处理不同的列表:关联列表、属性 列表、包含项目的列表...
虽然我真的很喜欢@jkiiski 的 json-bind
解决方案,但我想我也会添加以下选项。
如果查询路径在 compile-time 处已知,您可以使用以下宏
(defmacro report-get (report &optional key &rest keys)
(cond
((null key) report)
((integerp key) `(report-get (nth ,key ,report) ,@keys))
(t `(report-get (cdr (assoc ,key ,report)) ,@keys))))
示例:
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Rain"
CL-USER> (report-get *array-from-above* :coord :lon)
-123.12
CL-USER> (macroexpand '(report-get *array-from-above* :weather 0 :main))
(CDR (ASSOC :MAIN (NTH 0 (CDR (ASSOC :WEATHER *ARRAY-FROM-ABOVE*)))))
T
(report-get *array-from-above* :weather 0 :main)
中的0
是访问天气项集合中的第一项
编辑:忘了说 - 这个宏是 setf
可用的。
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Rain"
CL-USER> (setf (report-get *array-from-above* :weather 0 :main) "Sunny")
"Sunny"
CL-USER> (report-get *array-from-above* :weather 0 :main)
"Sunny"
可能对您的要求没有用,但很高兴知道。