【個人メモ】JSONのパースにjmespathを使ってみよう


# jmespath

![Screen Shot 2015-02-15 at 19.10.37.png](https://qiita-image-store.s3.amazonaws.com/0/3985/b3b75668-cde7-1e24-d47b-e835615fc44e.png)

JSONのパースには、[jq](http://stedolan.github.io/jq/)というツールが有名である。
が、昨日、[aws-cli](https://github.com/aws/aws-cli)にて、--query オプションにより
jmespath形式でのJSONパースを利用できる、ということで
[jmespath](http://jmespath.org/)そのものを試してみることにした。


# jmespathをインストール

pipでインストールできる。

```
> pip install —user jmespath
```

インストール後、コマンドラインツールとしてjpコマンドを実行することが可能になる

```bash
> echo '{"a": "100"}' | jp a
“100"
```

簡単だ。

aws-cliでの出力を例に取りながら、
jmespathでのオプションをさらっと見ていく

[JMESPath Tutorial](http://jmespath.org/tutorial.html)

に書かれている。

# 例)aws-cliを利用してec2 instanceのprivate ipアドレスのみを取得してみる

管理している同立しているサービス向けのインスタンスの台数が30台〜とかなると
IPアドレスを抜くにも一苦労だったりする。

## aws-cliでtext出力を行い、cutに食わせて出させる場合

```bash
aws ec2 describe-instances --filter "Name=tag:Role,Values=web-front" --output text | grep PRIVATEIPADDRESSES | cut -f3
10.0.1.1
10.0.1.2
10.0.1.3
10.0.1.4
10.0.1.5
...
10.0.1.50
```

こんな感じになってしまう。

## jmespathを利用してaws-cliのJSON出力をパースするようにする

```bash
aws ec2 describe-instances --filter "Name=tag:Role,Values=web-front" --query "Reservations[*].Instances[*].PrivateIpAddress[]”

[
  “10.0.1.1”,
  “10.0.1.2”,
  “10.0.1.3”,
  “10.0.1.4”,
  “10.0.1.5”,
  …
  “10.0.1.50”
]
```

# 便利組み込み関数を利用してみる

aws cliがjmespathのクエリパスをサポートしているのは確認できた。
その他、jmespathに組み込まれているsearch関数を利用してみよう

##  search関数

[search関数](http://jmespath.readthedocs.org/en/latest/specification.html)は表現力が高い。
Hashのキーマッチングやフィルタリングに対応している。

## たとえば

以下のJSONデータがあるとする

```json
[
  {
    "name": "Keiji Matsuzaki",
    "age": 33,
    "language":
    [
      "Japanese"
    ],
    "programming": {
        "language":
          [
            "PHP",
            "Python",
            "Go",
            "Ruby"
          ]
    }
  },
  {
    "name": "Fujisan Taro",
    "age": 24,
    "language":
    [
      "Japanese"
    ],
    "programming": {
        "language":
          [
            "Rust",
            "C++",
            "Erlang",
            "Go"
          ]
    }
  },
  {
    "name": "Lim Chow",
    "age": 28,
    "language":
    [
      "English",
      "Chinese"
    ],
    "programming": {
        "language":
          [
            "Ruby",
            "sass",
            "Lua",
            "C"
          ]
    }
  },
  {
    "name": "Jem Tomas",
    "age": 52,
    "language":
    [
      "English",
      "French"
    ],
    "programming": {
        "language":
          [
            "C",
            "BASIC",
            "NASM",
            "HTML",
            "Go",
            "Haskell"
          ]
    }
  }
]
```

ここから、以下の条件に合致するデータを取り出す

### language: englishが含まれているレコードはどれぐらいあるか

jmespathでパースする場合はこうなる

```bash
jp -f test.json 'length([].language[?@==`English`][])'
> 2
```

#### どういうことか

**[].language[]** で、languageの要素を取得する。

```json
[
    [
        "Japanese"
    ],
    [
        "Japanese"
    ],
    [
        "English",
        "Chinese"
    ],
    [
        "English",
        "French"
    ]
]
```

#### language[]のものから、current node == `English`に合致するものを検索する

**[].language[?@==`English`]**

```json
[
    [],
    [],
    [
        "English"
    ],
    [
        "English"
    ]
]
```

#### arrayごとに分割されているのでflatten化する

**[].language[?@==`English`][]**

```json
[
    "English",
    "English"
]
```

#### length関数により、要素数を数える

**length([].language[?@==`English`][])**

```
> 2
```

なお、jpコマンドはデフォルトでsearch関数を利用するようになっている。
[jp#43](https://github.com/jmespath/jmespath.py/blob/0835c1eba492d2e36c1ea47f6dfee2e6a92c1ef5/bin/jp#L43)

### 他

#### 年齢の平均

```bash
jp -f test.json 'avg([].age)’
> 34.25
```

#### 年齢が一番高い人

```bash
jp -f test.json 'max_by([], &age)’
```

```json
{
    "age": 52,
    "programming": {
        "language": [
            "C",
            "BASIC",
            "NASM",
            "HTML",
            "Go",
            "Haskell"
        ]
    },
    "name": "Jem Tomas",
    "language": [
        "English",
        "French"
    ]
}
```

それ以外にも関数はあるけど、書いてて時間が足りないので
必要になったら使ってみよう

[jmespathで利用できる関数一覧](http://jmespath.org/specification.html#built-in-functions)