Easily Get JSON Data in Lisp

2014-10-06 update: Recursive way.

Use Emacs build-in package json.el and its function (json-read-from-string JSON-STRING), you can get a parsed nested list like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
((title . "了")
(stroke_count . 2)
(radical . "⼅")
(non_radical_stroke_count . 1)
(heteronyms . [((pinyin . "li ǎ o")
(definitions . [((type . "動")
(quote . ["宋.陸游.醉歌:「心雖了是非,口不給唯諾。」"])
(example . ["如:「一目了然」。"])
(def . "明白、懂得。"))
((type . "動")
(quote . ["老殘遊記.第十九回:「今日大案已了,我明日一早進城銷差去了。」"])
(example . ["如:「不了了之」、「責任未了」。"])
(def . "完畢、結束。"))
((type . "副")
(example . ["如:「了無新意」、「了無生趣」。"])
(def . "完全。與否定語「不」、「無」等連用。有「一點也不……」的意思。"))
((type . "副")
(example . ["如:「辦得了」、「寫不了」。"])
(def . "與「得」、「不」等連用,表示可能或不可能。"))
((type . "形")
(example . ["如:「小時了了,大未必佳。」"])
(def . "聰明、慧黠。"))])
(bopomofo2 . "li ǎ u")
(bopomofo . "ㄌㄧㄠˇ"))
((pinyin . "le")
(definitions . [((type . "助")
(quote . ["宋.蘇軾.念奴嬌.大江東去詞:「遙想公瑾當年,小喬初嫁了,雄姿英發。」"])
(example . ["如:「到了」、「天黑了」、「吃了再走」。"])
(def . "置於動詞後,表示動作的結束。"))
((type . "助")
(example . ["如:「走了,還談這些幹什麼?」、「別哭了,事情會好轉的。」、「好了,吵了一天還不夠!」"])
(def . "置於句末或句中停頓處。表示不耐煩、勸止等意思。"))])
(bopomofo2 . "le")
(bopomofo . "˙ㄌㄜ"))]))

Now try to get the data in it. In JS, you can get any data in JSON easily with data.key[n]...., but json.el seems not to provide an easy way to get data iterately from it; and a lot of duplicated (cdr (assq 'key data)) and (aref data 0) make code hardly readable.

So let’s write a function:

Iteration Way

1
2
3
4
5
6
7
(defun json-getdata (data &rest args)
(do ((i 0 (1+ i)))
((> i (1- (length args))) data)
(cond ((symbolp (elt args i))
(setq data (cdr (assq (elt args i) data))))
((numberp (elt args i))
(setq data (aref data (elt args i)))))))

Now you can get data from JSON like this:

1
(json-getdata data 'heteronyms 0 'definitions)

It’s nearly the equivalent of (in JS):

1
data.heteronyms[0].definitions

Use loop to get n data in vector, and collect all results as a list (like mapcar):

1
2
(loop for i from 0 to 4 collect
(json-getdata data 'heteronyms 0 'definitions i))

Recursive Way

At first, I tried to write this function in recursive way. However I just couldn’t done this mission which nearly made me insane. Now I found that’s because my unfamiliarity of two concepts: &rest and apply:

1
2
3
4
5
6
7
8
9
;; Recursive way
(defun json-get (data &rest keys)
(cond ((null (car keys))
data)
((symbolp (car keys))
(apply #'json-get (cdr (assq (car keys) data)) (cdr keys)))
((integerp (car keys))
(apply #'json-get (aref data (car keys)) (cdr keys)))
))

Details

The &rest arguments will return as a list:

1
2
3
4
5
6
(defun keys-test (data &rest keys)
keys)
(keya-test DATA 1 2) ; keys => (1 2)
(keys-test DATA ()) ; keys => (nil)
(car '(nil)) ; => nil
(cdr '(nil)) ; => nil

So, if you just write recursively calls like this:

1
...(json-get (cdr (assq (car keys) data)) (cdr keys))...

Will goes wrong. Because it will be called (recursively) like this:

1
(json-get DEEPTH-2-DATA ('key2 'key3))

instead of:

1
(json-get DEEPTH-2-DATA 'key2 'key3)

So apply is needed:

1
2
3
(apply #'+ '(1 2 3))     => 6  ;the last argument of `apply' should be a list
(apply #'+ 1 2 '(1 2 3)) => 9
(funcall #'+ 1 2 3) => 6 ;Don't confound `funcall' with `apply' !!!!!!!

[2014-10-06 月 04:12] - Add recursive way.