PythonでJSONPathを扱えるjsonpath-ngライブラリを使ってみる

2022.10.05

はじめに

データアナリティクス事業本部のkobayashiです。

PythonでJSON形式のデータを扱うことがあり含まれているデータを簡単に取り扱いと思いJSONPathを使えないかと探していたところ該当するライブラリがあったので試してみました。

環境

  • Python 3.9.10

JSONPathとは

JSONPathは、JSONからデータを取り出す仕組みでクエリを使って要素を取り出す方法です。これにより複雑な構造のJSONデータからでも目的の要素を簡単に取得することができます。

jsonpath-ngライブラリを使ってみる

PythonでJSONPathを扱うライブラリはjsonpath-ng · PyPI になります。jsonpath-ngはJSONPathの定義 に従ってその仕様を忠実に再現しているためPythonでJSONPathを扱うにはファーストチョイスになるかと思います。

それでは早速使ってみたいと思います。

jsonpath-ngをインストール

はじめにjsonpath-ngをインストールしますpipでインストールできるので以下のコマンドだけで完了です。

$ pip install jsonpath-ng

jsonpath-ngはPythonの2.6, 2.7 & 3.xで検証されているので大抵のPythonでは動作するかと思います。

jsonpath-ngを使ってみる

実際にjsonpath-ngでJSONPathを使ってデータを取得してみます。取り出すものデータはJSONPath - XPath for JSON で使われている以下のJSONを扱ってみます。

{"store": {
    "book": [
        {"category": "reference",
         "author": "Nigel Rees",
         "title": "Sayings of the Century",
         "price": 8.95
         },
        {"category": "fiction",
         "author": "Evelyn Waugh",
         "title": "Sword of Honour",
         "price": 12.99
         },
        {"category": "fiction",
         "author": "Herman Melville",
         "title": "Moby Dick",
         "isbn": "0-553-21311-3",
         "price": 8.99
         },
        {"category": "fiction",
         "author": "J. R. R. Tolkien",
         "title": "The Lord of the Rings",
         "isbn": "0-395-19395-8",
         "price": 22.99
         }
    ],
    "bicycle": {
        "color": "red",
        "price": 19.95
    }
}
}

このデータを使ってJSONPath - XPath for JSON のexampleと同じJSONPathを指定してデータを取り出してみます。

JSONPath Result
$.store.book[*].author the authors of all books in the store
$..author all authors
$.store.* all things in store, which are some books and a red bicycle.
$.store..price the price of everything in the store.
$..book[2] the third book
$..book[(@.length-1)]
$..book[-1:]
the last book in order.
$..book[0,1]
$..book[:2]
the first two books
$..* all Elements in XML document. All members of JSON structure.
$..book[?(@.isbn)] filter all books with isbn number
$..book[?(@.price<10)] filter all books cheapier than 10

はじめにスクリプト?()を使わないJSONPathをjsonpath-ngで使用してみます。

from jsonpath_ng import parse

json_raw = {上記のJSON}

# the authors of all books in the store
json_match = parse('$.store.book[*].author').find(json_raw)
print([v.value for v in json_match])

# all authors
json_match = parse('$..author').find(json_raw)
print([v.value for v in json_match])

# all things in store, which are some books and a red bicycle.
json_match = parse('$.store.*').find(json_raw)
print([v.value for v in json_match])

# the price of everything in the store.
json_match = parse('$.store..price').find(json_raw)
print([v.value for v in json_match])

# the third book
json_match = parse('$..book[2]').find(json_raw)
print([v.value for v in json_match])

# the last book in order.
json_match = parse('$..book[-1:]').find(json_raw)
print([v.value for v in json_match])

# the first two books
json_match = parse('$..book[:2]').find(json_raw)
print([v.value for v in json_match])

# all Elements in XML document. All members of JSON structure.
json_match = parse('$..*').find(json_raw)
print([v.value for v in json_match])

実行結果

# the authors of all books in the store
['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']

# all authors
['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']

# all things in store, which are some books and a red bicycle.
[[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}], {'color': 'red', 'price': 19.95}]

# the price of everything in the store.
[8.95, 12.99, 8.99, 22.99, 19.95]

# the third book
[{'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}]

# the last book in order.
[{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]

# the first two books
[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}]

# all Elements in XML document. All members of JSON structure.
[{'book': [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}], 'bicycle': {'color': 'red', 'price': 19.95}}, [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}], {'color': 'red', 'price': 19.95}, 'reference', 'Nigel Rees', 'Sayings of the Century', 8.95, 'fiction', 'Evelyn Waugh', 'Sword of Honour', 12.99, 'fiction', 'Herman Melville', 'Moby Dick', '0-553-21311-3', 8.99, 'fiction', 'J. R. R. Tolkien', 'The Lord of the Rings', '0-395-19395-8', 22.99, 'red', 19.95]

$..book[(@.length-1)]$..book[0,1]に関してはjsonpath-ngでこの型式が使えないためエラーとなってしまいますが、他の形式ではほぼJSONPathのExampleと同じ記述で同一のデータを取得できました。

次にスクリプト?()を使った型式をjsonpath-ngで使うには拡張機能のextended parserを使う必要があるためimportするモジュールも変更する必要があります。

from jsonpath_ng.ext import parse

json_raw = {上記のJSON}

# filter all books with isbn number
json_match = parse('$..book[?(@.isbn)]').find(json_raw)
print([v.value for v in json_match])

# filter all books cheapier than 10
json_match = parse('$..book[?(@.price<10)]').find(json_raw)
print([v.value for v in json_match])

実行結果

# filter all books with isbn number
[{'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]

# filter all books cheapier than 10
[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}]

こちらもJSONPathのExampleと同じ記述で同一データが取得できました。拡張機能を使うためのjsonpath_ng.extモジュールは以下の型式を使う場合に必要になりますので特にこだわりが無ければはじめからjsonpath_ng.extモジュールをimportしてしまうのが良いかもしれません。

  • len
  • sub
  • split
  • filter
  • arithmetic

まとめ

PythonでJSONPathを使えるjsonpath-ngライブラリを試してみました。ほぼJSONPathで定義されている表現が使えるため、Pythonで複雑なJSONデータを扱う場合はこのライブラリを使うと幸せになれると思います。

最後まで読んで頂いてありがとうございました。