なにもわからない

気分で技術系の雑記を書きます

jq で特定条件にマッチする要素を置換する

こういった JSON.name == "Michel".attributes だけを置換して全体を出力したい。

/tmp
$ cat test.json
{
  "elements": [
    {
      "name": "John",
      "attributes": {
        "private": true,
        "last_login": "2019-11-01 00:00:00"
      }
    },
    {
      "name": "Michel",
      "attributes": {
        "private": false,
        "last_login": "2019-11-02 00:00:00"
      }
    },
    {
      "name": "Paul",
      "attributes": {
        "private": false,
        "last_login": "2019-11-31 00:00:00"
      }
    }
  ]
}

map(), select(), |=, //, . を駆使して実現できる。すごく雑に書くと

  • map() => .elements[] のような配列を map() する
  • select() => 条件に一致するものだけフィルタする
  • // => a // ba ? a : b と評価される演算子
  • . => イテレートしている要素

キモは //

A filter of the form a // b produces the same results as a, if a produces results other than false and null. Otherwise, a // b produces the same results as b.

This is useful for providing defaults: .foo // 1 will evaluate to 1 if there’s no .foo element in the input. It’s similar to how or is sometimes used in Python (jq’s or operator is reserved for strictly Boolean operations).

stedolan.github.io


これらは以下の様に組み合わせて使う。

# 条件にマッチしない場合 `.` (=`.attributes`) に置換する(≒何もしない)
/tmp
$ cat test.json | jq '.elements |= map((select(0)) // .)'
{
  "elements": [
    {
      "name": "John",
      "attributes": {
        "private": true,
        "last_login": "2019-11-01 00:00:00"
      }
    },
    {
      "name": "Michel",
      "attributes": {
        "private": false,
        "last_login": "2019-11-02 00:00:00"
      }
    },
    {
      "name": "Paul",
      "attributes": {
        "private": false,
        "last_login": "2019-11-31 00:00:00"
      }
    }
  ]
}

# 条件にマッチする場合 `elements[].attributes` を `[]` に置換する
/tmp
$ cat test.json | jq '.elements |= map((select(1).attributes |= []) // .)'
{
  "elements": [
    {
      "name": "John",
      "attributes": []
    },
    {
      "name": "Michel",
      "attributes": []
    },
    {
      "name": "Paul",
      "attributes": []
    }
  ]
}

そして特定条件時のみ .attributes を置換する

/tmp
$ cat test.json | jq '.elements |= map((select(.name == "Michel").attributes |= { private: true, last_login: "2222-22-22 22:22:22"}) // .)'
{
  "elements": [
    {
      "name": "John",
      "attributes": {
        "private": true,
        "last_login": "2019-11-01 00:00:00"
      }
    },
    {
      "name": "Michel",
      "attributes": {
        "private": true,
        "last_login": "2222-22-22 22:22:22"
      }
    },
    {
      "name": "Paul",
      "attributes": {
        "private": false,
        "last_login": "2019-11-31 00:00:00"
      }
    }
  ]
}

202X年もシェル芸は身を助けてくれそうです