JSONのパース・生成


Tags: JSON, cl-json, yason

JSONを扱う場合、専用のライブラリを使うと簡単です。

CL-JSON

JSONデータとCommon Lispのオブジェクトを変換するためのライブラリです。動作を細かく変更できるようになっているので、幅広いケースに対応できるでしょう。

;;; JSON文字列の変換

(json:decode-json-from-string "{\"x\": 0, \"y\": 1}")
;=> ((:X . 0) (:Y . 1))

(json:encode-json-to-string '((:x . 0) (:y . 1)))
;=> "{\"x\":0,\"y\":1}"

;;; ストリームからの読み込みと書き出し

(with-input-from-string (s "{\"x\": 0, \"y\": 1}")
  (json:decode-json s))
;=> ((:X . 0) (:Y . 1))

(with-output-to-string (s)
  (json:encode-json '((:x . 0) (:y . 1)) s))
;=> "{\"x\":0,\"y\":1}"

YASON

CL-JSONと目的やできることが似ていますが、単純な仕組みになっています。JSONデータからCLOSオブジェクトへの直接的な変換はサポートしていません。

JSONデータをパースするにはyason:parseを使います。

(let (l (ht (yason:parse "{\"x\": 0, \"y\": 1}")))
  (maphash (lambda (k v) (push (cons k v) l)) ht)
  (values ht l))
;=> #<HASH-TABLE :TEST EQUAL size 2/60 #x302000F52B0D>,
;   (("y" . 1) ("x" . 0))

yason:parseに入力として指定できるのは文字列ストリームのどちらかです。

(let ((ht (yason:parse "{\"x\": 0, \"y\": 1}")))
  (maphash (lambda (k v) (format t "~a, ~a~%" k v)) ht))
;-> x, 0
;   y, 1

(with-input-from-string (s "{\"x\": 0, \"y\": 1}")
  (let ((ht (yason:parse s)))
    (maphash (lambda (k v) (format t "~a, ~a~%" k v)) ht)))
;-> x, 0
;   y, 1

JSONオブジェクトは

に変換できます。JSONデータとCommon Lispオブジェクトの対応の詳細はMapping between JSON and CL datatypesを見てください。

;; 標準では比較関数がcl:equalのハッシュテーブルに変換する
(yason:parse "{\"x\": 0, \"y\": 1}")
;=> #<HASH-TABLE :TEST EQUAL size 2/60 #x302000F1E5DD>

;; 連想リストに変換する。:object-asで作るオブジェクトの種類を決められる
(yason:parse "{\"x\": 0, \"y\": 1}" :object-as :alist)
;=> (("y" . 1) ("x" . 0))

;; プロパティリストに変換する。yason:*parse-object-as*スペシャル変数でもコントロールできる
(let ((yason:*parse-object-as* :plist))
  (yason:parse "{\"x\": 0, \"y\": 1}"))
;=> ("x" 0 "y" 1)

JSONデータへのエンコードには

を使います。エンコードされた結果はストリームに書き出されます。

;; 基本的にyason:encodeでエンコードする
(let ((ht (make-hash-table :test #'equal)))
  (setf (gethash "x" ht) 0)
  (setf (gethash "y" ht) 1)
  (yason:encode ht *standard-output*))
;-> {"x":0,"y":1}

;; ストリームの指定を省略すると標準出力へ
(let ((ht (make-hash-table :test #'equal)))
  (setf (gethash "x" ht) 0)
  (setf (gethash "y" ht) 1)
  (yason:encode ht))
;-> {"x":0,"y":1}

;; 連想リストからのエンコードにはencode-alistを使う
;; 連想リストのキーはcl:symbol-nameで文字列に変換される
(yason:encode-alist '((:x . 0) (:y . 1)))
;-> {"X":0,"Y":1}

;; JSONオブジェクトのキーを小文字にしたい場合は小文字のシンボルを使う
(yason:encode-alist '((:|x| . 0) (:|y| . 1)))
;-> {"x":0,"y":1}

;; プロパティリストからのエンコードにはencode-plistを使う
(yason:encode-plist '(:x 0 :y 1))
;-> {"X":0,"Y":1}

yason:encodeは総称関数なので、メソッドを定義することでユーザ定義のCLOSオブジェクトを直接エンコードできるようになります。

;; 連想リストを表わすクラスを定義する
(defclass alist () ((content :initarg :content)))

;; エンコードのために特殊化したメソッドを定義する
(defmethod yason:encode ((object alist) &optional (stream *standard-output*))
  (yason:encode-alist (slot-value object 'content) stream))

;; オブジェクトを直接変換できるように
(yason:encode (make-instance 'alist :content '((:x . 0) (:y . 1))))
;-> {"X":0,"Y":1}