2013年6月17日月曜日

JSONと仲良くする

最近大量だったり巨大だったりなJSONファイルから必要な部分を探す必要に迫られたときにjqが便利だったので紹介。



http://stedolan.github.io/jq/
表示する範囲を絞り込めるので、繰り返し同じ場所を表示したいときとかに便利。
抽出結果がJSONになるので一部分を抽出したJSONを作成したりできる。
クエリがちょっと独特な気がする。

sample.json
{"1":{"name": "data1","point":1001,"optional":1},"2":{"name":"data2","point":1002},"3":{"name":"data3","point":1003,"optional":3}}

使い方例
・整形して表示する
jq '.' sample.json
・nameだけを抜き出す
jq '.[].name' sample.json
・nameとoptionalを抜き出す、どっちかがないやつはnullになる
jq '.[]|{name, optional}' sample.json
・トップレベルの"1"のオブジェクトだけを抽出する
jq '.["1"]' sample.json
・findとxargsと組み合わせたときにファイル名も付ける、特定の要素がnullになってる項目を含むファイルを探すときに便利
find -name '*.json' | xargs -I{} jq -c '.[]|{name,optional,filename: $filename}' --arg filename {} {}
・各要素の要素数を取得
jq '.[]|length' sample.json
・トップレベルの要素数を取得
jq 'keys|length' sample.json



jq以外の方法
JSONPath
プログラムからJson形式の検索や操作を行うためのライブラリというか仕様。
XPathをJSON用に変更したものらしい?
いろいろな言語用にライブラリがある、メジャーな言語にはほとんどありそう。
プログラムの中でJSONの一部を使うときとかは便利そうなんだけど、
ちょっと試して動かなかったのでわからない。

上のsample.jsonの1のnameを検索する場合はこんな感じだと思う。
"$..1.name"



行指向に変換してみる

jqは便利だけど独自クエリが思い出せなかったり、
コマンドラインツールとの相性がいまいち。


ということで、さっきのsample.jsonをこんな風に変換するスクリプトを作ってみた。
/1/name                                  => "data1"
/1/point                                 => 1001
/1/optional                              => 1
/2/name                                  => "data2"
/2/point                                 => 1002
/3/name                                  => "data3"
/3/point                                 => 1003
/3/optional                              => 3

こうなったら、grepとかで好きなように検索できる。


#!/usr/bin/php
<?php
function jr($data, $path) {
    if (is_array($data)) {
        foreach ($data as $key => $value) {
            jr($value, $path . '[' . $key . ']');
        }
    } elseif (is_object($data)) {
        foreach($data as $key => $value) {
            jr($value, $path . '/' . $key);
        }
    } else {
        $output = is_string($data) ? '"' . $data . '"' : $data;
        printf("%-40s => %s\n", $path, $output);
    }
}

$input = (count($argv) > 1) ? $argv[1] : 'php://stdin';
$all_data = json_decode(file_get_contents($input), false);
jr($all_data, '');



作ってはみたけど、jqとjr(上のスクリプトの仮称)で必要なコマンドを比較してみたら
jqの使いやすさを再確認するだけになった。
正規表現だとややこしくなる箇所もあるし。
でも、独自クエリがなかなか覚えられない人にはjr+grepも便利。

比較
トップレベルのキー一覧を取得する
$jq 'keys' ~/work/sample.json
$jr.php ~/work/sample.json | sed -e 's|^/\([^/]*\)/.*|\1|' | uniq

第二階層のnameの一覧を取得する
$jq '.[].name' ~/work/sample.json
$jr.php ~/work/sample.json | grep '/\w*/name\s' | sed -e 's/.*=> //'

どっかにあるnameがdata2のデータを探す
$jq '.[].name == "data2"' ~/work/sample.json
false
true
false

$jr.php ~/work/sample.json | grep "data2"
/2/name                                  => "data2"




その他
最近の言語はだいたいJSONにオブジェクトとか配列を保存できる機能があるので、
JSONをうまく扱えるとそういうのを読むのにも便利。

S式とかXMLとかyamlとかテキストの構造化データ形式が色々あるので、
自分が読み書きしやすいやつと相互変換させたりするのもいいかも。


clojureなら

(clojure.data.json/json-str
  {1 {:name "data1", :point 1001, "optional" 1}
   2 {:name "data2", :point 1002}
   3 {:name "data3", :point 1003, "optional" 3}})
で上のsample.jsonと同じのができる。
emacsだとS式編集用に便利な機能がいろいろあったり、pareditで括弧間違えなくて便利。


あとはJaqlとかJSONiqとかJSONをSQL風なクエリで検索したりするのもある。

0 件のコメント:

コメントを投稿