notebook

都内でWEB系エンジニアやってます。

jqで特定リストの中のどれかにマッチするか?という条件を扱う

Rubyでいうinclude?的なことをやりたかったがさっとできなかったのでメモを残す

サンプルはこんな感じ

$ cat sample.json
[
  {
    "user": "hoge",
    "team": ""
  },
  {
    "user": "fuga",
    "team": ""
  },
  {
    "user": "piyo",
    "team": ""
  },
  {
    "user": "foo",
    "team": ""
  },
  {
    "user": "bar",
    "team": ""
  }
]

hoge,fugaはteam: Aで、foo,barはteam: Bでという感じで値を入れていく場合

ぱっとマニュアル見た感じはinsidecontainsかなと思ったので色々試してみたもののうまく行かず

いったんおさらい

contains

条件が完全に含まれる場合はtrueを返す

例を見ると単純な文字列比較のときはわかりやすいが配列やハッシュが含まれると理解が追いつかなくなる…

条件で指定する文字列が入力に含まれてますか?ということらしい

文字列比較の場合は部分一致、配列やハッシュの場合はその構造が包含されるか

$ echo '"foobar"' | jq 'contains("bar")'
true

入力foobarbarという文字列が含まれていますか?という感じ

inside

containsの逆、これは入力と条件が逆という意味合いっぽい

文面読んでサンプル見ただけだと「分かりづらい…」と感じたけど実際に試してみると理解しやすかった

$ echo '"bar"' | jq 'inside("foobar")'
true

こういうことか!という感じ

containsも同様だが、入力と同じ型でないと比較ができないよう

$ echo '"fuga"' | jq 'inside(["fuga"])'
jq: error (at <stdin>:1): array (["fuga"]) and string ("fuga") cannot have their containment checked

型をそろえてあげればOK

$ echo '["fuga"]' | jq 'inside(["hoge","fuga"])'
true

ということで冒頭の例を実現できるように書くと次のようになる

cat sample.json| jq '.[]|if([.user]|inside(["hoge","fuga"])) then .team="A" elif([.user]|inside(["foo","bar"])) then .team="B" else . end'
{
  "user": "hoge",
  "team": "A"
}
{
  "user": "fuga",
  "team": "A"
}
{
  "user": "piyo",
  "team": ""
}
{
  "user": "foo",
  "team": "B"
}
{
  "user": "bar",
  "team": "B"
}

他の解決パターン

最初こっちのパターンで処理していた

testを使って行うことも可能

cat sample.json | jq '.[]|if(.user|test("(hoge|fuga)")) then .team="A" elif(.user|test("(foo|bar)")) then .team="B" else . end'
{
  "user": "hoge",
  "team": "A"
}
{
  "user": "fuga",
  "team": "A"
}
{
  "user": "piyo",
  "team": ""
}
{
  "user": "foo",
  "team": "B"
}
{
  "user": "bar",
  "team": "B"
}

まとめ

右往左往したけど正規表現で色々やる必要がなければinsideのほうが良さそうに感じた

また1つjqレベルが上がりました