Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

jqの.[]チョットワカルになってきた

普段なんとなくjqを使うといったら.fooくらいの単純なフィルタや整形用途くらいで、ガッツリ勉強したことはなかった。

ガッツリ勉強していないので、.[]といった配列まわりの機能をゴリゴリ使えなかったのだが、基礎テクを学び直してある程度使えるようになっておこうと思った。そのメモ。

このメモは網羅的な物ではなく、基礎的でよく使いそうな項目だけ記載してある。

また、普段はScalaをメインに書いていて、一番理解しやすいのがScalaなので、Scalaでいうところのこれはこう書く、というふうにメモしてある。

Scalaのアレはこれ

Seq() / new

配列を作るにはそのまま[]を使う。

Seq(1,2,3)
// jq
[1, 2, 3]

同様に、{}でオブジェクトを作ることができる。

// jq
{ foo: 42 }

JSONとは異なり、jqではキーのクオートを省略してもよい。

map

map操作を行うにはspread演算子.[]を利用し、フィルタにつなぎ、それで配列を構築すればよい。

.[]は公式にはIteratorと呼ばれているが、この記事では慣用的にspreadと呼ぶことがある。

stedolan.github.io

arr.map(_.foo)
// jq
[.[].foo]

note: .[].ほげはperlのmapに近い

// jq
[.[].foo, .[].bar]

こうすると1つの配列に押し込まれる。

ちなみにより簡便な方法として、配列からそのまま配列に変換するには、mapをそのまま使う(spreadの振る舞いを確認するためにあえて後から説明した)。

// jq
map(.foo)

spread演算子を使う方法と同様に1つの配列に押し込めることができる。

// jq
map(.foo, .bar)

note: map演算子はspreadしてconstructすることと同等である。

一般に、[.[] | foo]の形になった場合はmapで書ける。

flaten

[[123, 456], [789, 012]]を1つのArrayにつぶしたいとき。

mapとspread演算子について知っていればわかりやすい。

// jq
map(.[])

merge / &&&

CatsのArrowのmergeとjqの,はおおむね同じ働きをする。同一の内容を複数のフィルタに投入して、それぞれの結果を返すのだ。

val f = (n: Int) => n * 2
val g = (n: Int) => Seq(n, n)

val h = f &&& g
h(42) // => (84, Seq(42, 42))
$ echo "42" | jq '[. * 2, [., .]]'
[
  84,
  [
    42,
    42
  ]
]

filter

filterするにはselectすればよい。ただし、select自体は配列を受け取らないため、いちどspreadしてからpipeする。

$ echo '[1,2,3,4,5,6,7,8,9]' | jq '.[] | select(. % 2 == 0)'
2
4
6
8

また、最終的な結果が配列の場合はmapを組み合わせても書ける。

echo '[1,2,3,4,5,6,7,8,9]' | jq 'map(select(. % 2 == 0))'
[2, 4, 6, 8]

string interpolation

\()を使うとよい。

echo '[1,2,3]' | jq 'map("number is \(.)")'
[
  "number is 1",
  "number is 2",
  "number is 3"
]

|: pipe

フィルタを結合する。.foo.barのように、自然と左右に並べられるフィルタもあるが、並べられないフィルタはpipeで結合する。

ポイントフリーならぬポイント有りのスタイルに書き換えられる。

最終的な出力の形をテンプレート的に一番後ろにもってきて、pipeで値をもらう、というのがよくあるイディオムらしい。

// jq
foo | bar | {buzz: .piyo, hoge: .fuga}

.. -- 再帰抽出

ワイルドカード的に構造をスキップして子の値をすべて取出そうとする。

$ echo '{"foo": {"bar": {"buzz": {"piyo": 42}}}}' | jq '..|.piyo?|numbers'
42

グロブの**に近いが、値に限らず配列やオブジェクトも取出そうとする。全走査といったほうがよい。

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

CLI options

-r: raw出力 -- ダブルクオートを忘却する

出力される文字列のダブルクオートを捨てる。

$ echo '["foo", "bar"]' | jq '.[]' -r
foo
bar

相補的な機能として-Rが存在する。

-R: raw入力 -- ダブルクオートを追加する

入力されるJSONの前処理として、「各行を文字列とみなし、それらの配列を構成する」という処理を行う。

$ echo "foo\nbar" | jq -R
"foo"
"bar"

cf. -s

-s: slurp入力 -- 入力全体を配列とみなす

入力\n入力2の代わりに、[入力, 入力2]が入力されたものとみなす。

echo '"foo"\n"bar"' | jq -s
[
  "foo",
  "bar"
]

ただし、-R-sとは同時に複合できないため、二度jqを呼ぶ必要がある。

$ echo "foo\nbar" | jq -R | jq -s
[
  "foo",
  "bar"
]
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?