トラック
/
jq
jq
/
演習
/
買い物リスト
買い物リスト

買い物リスト

学習演習

はじめに

基本

JSONの構文とデータ型については、すでに知っていることを前提に進めます。

jqは、入ってきたJSONデータを_単一の式_(_フィルターのパイプライン_として書きます)に通し、目的どおりに変換されたデータを生み出します。

Note

jqという言語は、jqという_プログラム_によって実装されています。 このプログラムには、入力をどのように消費し、出力をどのように表示するかを制御するための、便利なコマンドラインオプションがいくつか用意されています。

以下の例では、次のようなものが出てきます。

  • -nまたは--null-input

    通常、jqプログラムには読み込むファイルを渡すか、入力にデータを送ります。 --null-inputオプションを使うと、入力を一切与えずにJSONデータを生成できます。

  • -cまたは--compact-output

    jqはデフォルトで出力を整形して表示します。 人間がデータを見るときには、きれいに整形されているととても便利です。 しかし、機械にとっては必要ありません。--compact-outputオプションは整形用の空白を取り除き、生成されるJSONのサイズを最小限にします。

  • -f filenameまたは--from-file filename

    コマンドラインで指定する代わりに、filenameからjqプログラムを読み込みます。 sedとawkも同じ目的で-fオプションを使います。 これはプラクティス演習のテストスクリプトで使われているのを見ることになります。

このレッスンの残りでは、jqという_言語_に焦点を当てます。

フィルターとパイプ

フィルターは式とも呼ばれます。

_フィルター_は入力を受け取り、出力を生成します。 unixシェルで作業するときと同じように、|というパイプで_フィルター_同士をつなぎ、ある_フィルター_の出力を別の_フィルター_の入力につなげます。

恒等フィルター:.

これがもっとも単純な_フィルター_です。 入力をそのまま出力に渡すだけです。

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

配列

ここでは配列を扱うための簡単な紹介をします。 この話題は後ほど詳しく取り上げます。

_配列_の要素には角括弧([])でアクセスします。インデックスは0から始まります。

$ echo '[10, 20, 30]' | jq '.[1]'
20

_フィルター_は、式を[と]で囲むことで_配列_を作れます。

  • 要素のリストがあらかじめ決まっている場合:

    jq -n '[1, 2, 3]'
    
  • 要素のストリームを集める場合:たとえば、 rangeは数値のストリームを出力する関数です

    $ jq -n 'range(10; 70; 15)'
    10
    25
    40
    55
    

    []を使うと、式の結果を配列にまとめられます

    $ jq -c -n '[range(10; 70; 15)]'
    [10,25,40,55]
    

カンマも演算子

_カンマ_は、_配列_の要素を区切るだけの構文ではありません。 カンマは、ストリーム同士を連結する演算子です。

たとえば[1, 2, 3]は、3つの式1、2、3を連結した結果を、_配列_のコンストラクター[]でまとめる_フィルター_です。

上のrange(10; 70; 15)にあるセミコロンに気づきましたか? jqという言語では_カンマ_に特別な役割があるため、複数の引数を取る関数では、引数の区切りにセミコロンを使います。

オブジェクト

オブジェクトの簡単な紹介です。

多くのプログラミング言語と同様に、_オブジェクト_のプロパティにはドットでアクセスします

$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Note

角括弧は_オブジェクト_にも使えますが、その場合は文字列リテラルを引用符で囲む必要があります。 これは、スペースを含むキーを扱うための方法の1つです。

$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"

{}とkey: valueのペアで_オブジェクト_を構築できます。 _キー_が「単純な」文字列であれば、引用符は必要ありません。

jq -n '{question: (6 * 9), answer: 42}'

の出力は次のとおりです

{
  "question": 54,
  "answer": 42
}

_キー_を_式_として扱うには、そのキーを括弧(())で囲む必要があります(次も上の例と同じ出力になります)。

echo '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'

パイプライン

たとえば、次の内容のfile.jsonがあるとします

{
  "key1": "value1",
  "key2": [5, 15, 25]
}

key2_配列_の長さを計算してみましょう。

$ jq '.key2 | length' file.json
3

.key2という式の出力を、lengthへの入力として_パイプ_で渡しています。lengthは、予想どおり_配列_の要素数を出力します。

フィルターが入力を無視することもある

次の例では、入力のJSONデータは無視され、出力に影響を与えません。

$ echo '{"answer": 42}' | jq '6 * 9'
54

フィルターがデータのストリームを出力することもある

_フィルター_は複数の値を出力できます。 たとえば.[]という_フィルター_は、_配列_の各要素を個別の値として出力します。

$ jq -n -c '[1, 2, 3]'
[1,2,3]

$ jq -n -c '[1, 2, 3] | .[]'
1
2
3

このような_フィルター_を別の_フィルター_にパイプすると、2番目の_フィルター_が**値ごとに**実行されます。

$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6

これは暗黙の繰り返しのようなものです。 このテクニックを理解すると、とても強力なjqの_フィルター_が、驚くほど簡潔に書けることに気づくでしょう。

括弧

括弧は、演算の順序を確定するために部分式をまとめるのに使います。これは他の言語と同じです。 jqでは、括弧が必要になる場面が少し意外に思えるかもしれません。

たとえば、2つの要素を持つ_配列_を作りたいとします。9の平方根と、e_の1乗です。 2つの式はそれぞれ9 | sqrtと1 | expです。 出力は_配列 [3, 2.7...]になると期待します。

$ jq -n '[ 9|sqrt, 1|exp ]'
[
  20.085536923187668,
  2.718281828459045
]

なぜ期待どおりにならなかったのでしょうか? jqは次のように解釈します。

[ ((9|sqrt), 1) | exp ]

jqは2つの要素(3と1)からなるストリームを作り、それぞれがexpに与えられます。

expが入力として受け取る数値が1つだけになるようにする必要があります。 言い換えると、パイプがカンマより先に評価されるようにする必要があります。

$ jq -n '[ 9|sqrt, (1|exp) ]'
[
  3,
  2.718281828459045
]

関数と演算子

深くは立ち入りませんが(関数は別の演習で扱うテーマです)、ここで便利な組み込み関数をいくつか紹介します。

  • length

    配列を入力として受け取り、その配列の要素数を出力します。

    $ jq -n '[10, 20, 30, 40] | length'
    4
    
  • +

    この演算子は、オペランドの型によって異なる動作をします。 数値を加算し、 文字列を連結し、 配列を追加し、 オブジェクトをマージします。

    $ jq -c -n '
        3 + 4,
        "foo" + "bar",
        ["a", "b"] + ["c"],
        {"m": 10} + {"n": 20}
    '
    7
    "foobar"
    ["a","b","c"]
    {"m":10,"n":20}
    

    addは配列を受け取り、+のルールに従ってすべての要素を足し合わせた結果を返す関数です。 [1, 2, 3] | addは6を出力します。

  • map

    配列を入力、フィルターを引数として受け取り、各要素にそのフィルターを適用した配列を出力します

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    _ある_入力と、引数としてのフィルターを受け取ります。

    • 引数にフィルターを適用した結果がtrueになったら、入力をそのまま出力します
    • そうでなければ、_何も_出力しません(null値ではなく、本当に何も出力しません)

    たとえば、いくつかの数値の中から3で割り切れるものを選ぶには

    $ jq -n 'range(10) | select(. % 3 == 0)'
    0
    3
    6
    9
    

    rangeが数値の_ストリーム_を出力することを思い出してください。 selectは各数値ごとに1回呼び出されます。 式を「通過」した数値だけが出力されます。

    配列の要素を選びたいことはよくあります。 やり方はいくつかあります。

    入力が["Anne", "Bob", "Cathy", "Dave"]のとき、長さが4の名前を選ぶには次のようにします。

    • mapとselectを組み合わせる

      map(select(length == 4))
      
    • 配列を要素に分解し、そのストリームにselectを適用して、結果をまとめる

      [ .[] | select(length == 4) ]
      

コメント

コメントは#で始まり、行の終わりまで続きます。

説明

突然パンケーキが無性に食べたくなりました。 現代の技術に詳しい人らしく、パンケーキの材料の買い物リストをJSON形式で書くことにします。 リストの構造は次のとおりです。

{
  "name": "name of shopping list",
  "ingredients": [ ...list of ingredients... ],
  "optional ingredients": [ ...list of ingredients... ]
}

材料は、次のようなJSONオブジェクトで表します。

{
  "item": "flour",
  "amount": {
    "quantity": 2,
    "unit": "cup"
  }
}

材料には、代わりに使える別の品目の文字列を保持する、"substitute"という任意のプロパティがあります。

jqを使って買い物リストを調べる方法を見てみましょう

1. 買い物リストの名前を取り出す

買い物リストの"name"要素を出力する式を書きましょう。

2. 必要な材料の数を数える

必須の材料だけを数えましょう。任意の材料は含めないでください。

3. 砂糖はどのくらい?

砂糖の量を出力する式を書きましょう。必要なのは数値の部分だけです。

4. 代用できる材料をマッピングする

材料の中には代用できるものがあります(材料XがないときはYを使えます)。推奨される材料をその代用品に対応付けるJSONオブジェクトを出力しましょう。マッピングには任意の材料も含めてください。

GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
jq Exercism

買い物リストを始める準備はできましたか?

Exercismに登録すれば、12個のコンセプト75個の演習、そして本物の人間によるメンタリングとともに、jqを学んでマスターできます。すべて無料です。