Views: 17
JSONPathとPython環境準備
JSONPathは、JSONデータを検索およびフィルタリングするためのクエリ言語です。JSON PointerやXPathに触発され、特にJSONデータ構造の柔軟な操作を目的として設計されました。
1. JSONPathの標準化:RFC 9535
1.2. History (歴史)
This document is based on Stefan Gössner’s popular JSONPath proposal (dated 2007-02-21)
[JSONPath-orig], builds on the experience from the widespread deployment of its
implementations, and provides a normative specification for it.
本文書は、Stefan Gössnerによる著名なJSONPath提案(2007-02-21付け)
[JSONPath-orig]をベースとし、その実装が広く普及してきた経験に基づき、規範となる仕様を提供するものである。
1.2. History
詳細な情報は、以下の公式ページを参照してください:
RFC 9535は、JSONPathの標準化を目的とした公式な文書であり、IETFによって2024年に策定されました。JSONデータに対する複雑なクエリ操作を簡潔に表現するための表記法を提供します。
この仕様は、過去のDraft仕様(draft-ietf-jsonpath-base-21)を基に策定され、以下のような機能を特徴としています:
-
再帰検索(例:
$..author
) -
条件フィルタリング(例:
[?(@.price < 10)]
) -
配列範囲指定(例:
[:2]
)
2. JSONPath式の概要
1.4. Overview of JSONPath Expressions (JSONPath式の概要)
A JSONPath expression is applied to a JSON value, known as the query argument. The output is
a nodelist.
JSONPath 式は、クエリー引数として知られる JSON 値に適用されます。出力はノードリストです。A JSONPath expression consists of an identifier followed by a series of zero or more
segments, each of which contains one or more selectors.
JSONPath 式は、識別子に続く 0 個以上のセグメントで構成され、各セグメントには 1 つ以上のセレクタが含まれます。1.4.1. Identifiers (識別子)
The root node identifier $ refers to the root node of the query argument, i.e., to the
argument as a whole.
ルート・ノード識別子 $ は、クエリ引数のルート・ノード、つまり引数全体を指します。The current node identifier @ refers to the current node in the context of the evaluation of
a filter expression (Section 2.3.5).
現在のノード識別子 @ は、フィルタ式の評価のコンテキストにおける現在のノードを指します (セクション 2.3.5)。1.4.2. Segments (セグメント)
Segments select children ([<selectors>]) or descendants (..[<selectors>]) of an
input value.
セグメントは入力値の子 ([<selectors>]) または子孫 (.[<selectors>]) を選択します。Segments can use bracket notation, for example:
セグメントには括弧を使用します:$[‘store’][‘book’][0][‘title’]
or the more compact dot notation, for example:
あるいは、よりコンパクトなドット記法も使用できます:$.store.book[0].title
Bracket notation contains one or more (comma-separated) selectors of any kind. Selectors are
detailed in the next section.
ブラケット記法は、1つ以上の(カンマで区切られた)任意の種類のセレクタを含みます。セレクタについては、次のセクションで詳しく説明します。A JSONPath expression may use a combination of bracket and dot notations.
JSONPath式では、ブラケット表記とドット表記を組み合わせて使用できます。This document treats the bracket notations as canonical and defines the shorthand dot
notation in terms of bracket notation. Examples and descriptions use shorthand where
convenient.
このドキュメントでは、ブラケット表記を標準的なものとして扱い、ブラケット表記の観点から省略記法のドット表記を定義する。例や説明では、便利な場合には省略記法を使用する。1.4.3. Selectors (セレクタ)
A name selector, e.g., ‘name’, selects a named child of an object.
名前セレクタ、例えば’name’は、オブジェクトの名前付きの子を選択します。An index selector, e.g., 3, selects an indexed child of an array.
インデックス・セレクタ、例えば3は、配列のインデックス付きの子を選択します。In the expression [*], a wildcard * (Section 2.3.2) selects all children of a node, and in
the expression ..[*], it selects all descendants of a node.
式[*]では、ワイルドカード*(セクション2.3.2)はノードのすべての子を選択し、式 ..[*]ではノードのすべての子孫を選択します。An array slice start:end:step (Section 2.3.4) selects a series of elements from an array,
giving a start position, an end position, and an optional step value that moves the position
from the start to the end.
配列スライス start:end:step
(セクション2.3.4)は、配列から一連の要素を選択し、開始位置、終了位置、および開始位置から終了位置へ移動するオプションのステップ値を指定します。A filter expression ?<logical-expr> selects certain children of an object or array, as
in:
フィルタ式 ?<logical-expr> は、オブジェクトや配列の特定の子要素を選択します:$.store.book[?@.price < 10].title
1.4.4. Summary (概要)
Table 1 provides a brief overview of JSONPath syntax.
表 1 に、JSONPath 構文の概要を示します。Table 1: Overview of JSONPath Syntax (JSONPath構文の概要)
Table 1: Overview of JSONPath Syntax
Syntax Element (構文要素) Description 説明 $
root node identifier (Section 2.2)
ルート・ノード識別子 (セクション 2.2)
@
current node identifier (Section 2.3.5) (valid only within
filter selectors)現在のノード識別子 (セクション 2.3.5) (フィルタセレクタ内でのみ有効)
[<selectors>]
child segment (Section 2.5.1): selects zero or more children
of a node子セグメント (セクション2.5.1): ノードの0個以上の子を選択する
.name
shorthand for [‘name’]
[‘name’] の省略記法
.*
shorthand for [*]
[*]の省略形
..[<selectors>]
descendant segment (Section 2.5.2): selects zero or more
descendants of a node子孫セグメント (セクション 2.5.2): ノードのゼロ個以上の子孫を選択する。
..name
shorthand for ..[‘name’]
..[‘name’] の省略記法
..*
shorthand for ..[*]
..[*] の省略記法
‘name’
name selector (Section 2.3.1): selects a named child of an
object名前セレクタ(セクション2.3.1):オブジェクトの名前付き子を選択する
*
wildcard selector (Section 2.3.2): selects all children of a
nodeワイルドカードセレクタ (セクション 2.3.2): ノードのすべての子を選択します。
3
index selector (Section 2.3.3): selects an indexed child of an
array (from 0)インデックスセレクタ(セクション 2.3.3):配列のインデックス付きの子(0 から)を選択
0:100:5
array slice selector (Section 2.3.4): start:end:step for
arrays配列スライスセレクタ(セクション2.3.4):配列の開始:終了:ステップ
?<logical-expr>
filter selector (Section 2.3.5): selects particular children
using a logical expressionフィルタセレクタ (第2.3.5節): 論理式で特定の子を選択
length(@.foo)
function extension (Section 2.4): invokes a function in a
filter expression関数拡張 (第2.4節): フィルタ式で関数を呼び出す
www.DeepL.com/Translator(無料版)で翻訳しました。
3. JSON Pointerとの違い
JSON Pointer(RFC
6901)は、JSONオブジェクト内の特定の値を一意に指し示すシンプルな構文を提供します。しかし、条件付きのフィルタリングや範囲指定など、複雑な操作には不向きです。
4. XPathとの比較
XPathはXML文書のクエリ言語として広く利用されていますが、JSONPathはJSON構造に特化しています。XPathに似た構文要素(例:再帰検索や条件式)が採用されていますが、JSONデータの特性に合わせて簡略化されています。
5. Pythonライブラリの選択
5.1. ライブラリの調査
Pythonでは、JSONPathを実装するいくつかのライブラリが存在します。以下の主要なライブラリを調査しました:
-
jsonpath-ng
-
標準的なJSONPath構文をサポート。
-
再帰検索や単純な選択に対応。
-
条件フィルタリングには制限があるため、拡張が必要。
-
-
jsonpath-ng.ext
-
jsonpath-ngを拡張し、条件選択(
[?@.price < 10]
)などをサポート。 -
現在のところ、インストールに失敗することが多い(リポジトリの問題)。
-
-
jsonpath-python
-
他のライブラリに比べると簡易的。
-
標準JSONPath構文をサポート。
-
条件フィルタリングや範囲指定は独自実装が必要。
-
5.2. インストール時の問題
`jsonpath-ng.ext`のインストールを試みた際、以下のエラーに直面しました:
ERROR: Could not find a version that satisfies the requirement jsonpath-ng.ext
また、`jsonpath-ng`や`jsonpath-python`も一部の拡張機能に対応していないため、カスタム実装が必要でした。
5.3. 最終的な選択
最終的に、jsonpath-ngを選択しました。理由は以下の通りです:
-
標準的なJSONPath構文のサポート。
-
拡張可能な構造でカスタマイズが容易。
-
他のライブラリに比べ、Pythonコミュニティ内での使用例が多い。
6. JSONPathテストプログラムの詳細解説
6.1. サンプルJSONデータ
以下のJSONデータを使用して、JSONPathクエリをテストします。このデータは、RFC 9535 1.5. JSONPath Examples (JSONPathの例)
で示されたものを拡張しています。
The examples are based on the simple JSON value shown in Figure 1, representing a bookstore
(which also has a bicycle).
この例は、図1に示すシンプルなJSON値に基づいており、書店(自転車もある)を表している。
Figure 1: Example JSON Value
data = {
"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": 399},
}
}
Table 2 shows some JSONPath queries that might be applied to this example and their intended
results.
表 2 に、この例に適用される可能性のある JSONPath クエリーと、その意図する結果を示します。
Table 2: Example JSONPath Expressions and Their Intended Results When Applied to the Example JSON
Value
表 2: 例の JSON 値に適用される JSONPath 式とその意図する結果の例
JSONPath | Intended Result | 意図する結果 |
---|---|---|
$.store.book[*].author |
the authors of all books in the store |
ストア内のすべての本の著者 |
$..author |
all authors |
すべての著者 |
$.store.* |
all things in the store, which are some books and a red bicycle |
ストアにあるすべてのもの、つまりいくつかの本と赤い自転車 |
$.store..price |
the prices of everything in the store|店内のすべてのものの価格 |
$..book[2] |
the third book |
3冊目の本 |
$..book[2].author |
the third book’s author|3冊目の本の著者 |
$..book[2].publisher |
empty result: the third book does not have a “publisher” |
$..book[-1] |
the last book in order |
順番の最後の本 |
$..book[0,1] |
the first two books |
最初の2冊の本 |
$..book[:2] |
the first two books |
最初の2冊の本 |
$..book[?@.isbn] |
all books with an ISBN number |
ISBN番号のあるすべての本 |
$..book[?@.price<10] |
all books cheaper than 10 |
10より安いすべての本 |
$..* |
all member values and array elements contained in the input value |
入力値に含まれるすべてのメンバ値と配列要素 |
Table 2: Example JSONPath Expressions and Their Intended Results When Applied to the Example JSON
Value
表2:JSONPath式の例と、JSON値の例に適用した場合の意図する結果
6.2. 各関数の詳細解説
6.2.1. flatten関数
def flatten(results):
"""Flatten nested lists into a single list."""
flat = []
for item in results:
if isinstance(item, list):
flat.extend(flatten(item))
else:
flat.append(item)
return flat
-
目的: 入れ子構造になったリストを1次元のリストに変換します。
-
ポイント:
-
再帰的にリストを展開。
-
JSONPathクエリの結果がネストしている場合に便利です。
6.2.2. recursive_descent関数
def recursive_descent(data, key=None, handle_normal=True):
"""
Traverse the JSON structure recursively to find all instances of a given key.
If no key is provided, collect all elements (for recursive wildcards like $..*).
"""
if handle_normal and key:
query = f"$..{key}" if key != "*" else "$..*"
try:
jsonpath_expr = parse(query)
result = [match.value for match in jsonpath_expr.find(data)]
return result
except Exception:
pass # Fallback to explicit handling if JSONPath fails
results = []
if isinstance(data, dict):
for k, v in data.items():
if key is None or k == key:
results.append(v)
results.extend(recursive_descent(v, key, handle_normal=False))
elif isinstance(data, list):
for item in data:
results.extend(recursive_descent(item, key, handle_normal=False))
return results
-
目的: 再帰的にJSONデータを探索して指定されたキーを取得します。
-
特徴:
-
$..key
のような再帰的クエリをサポート。 -
JSONPathの失敗時にフォールバック処理を実装。
6.2.3. manual_filter関数
def manual_filter(data, condition):
"""
Apply a manual filter condition (e.g., @.price < 10) to a list of items.
Safely evaluates the condition for dictionary items.
"""
filtered = []
for item in data:
if isinstance(item, dict):
try:
# Replace @.key with item['key'] dynamically for safe access
condition_replaced = re.sub(r"@\.(\w+)", r'item["\1"]', condition)
if eval(condition_replaced):
filtered.append(item)
except (KeyError, AttributeError, SyntaxError):
# Skip items where the condition cannot be applied
pass
return filtered
-
目的: 条件フィルタリング(例:
[?@.price < 10]
)を実現します。 -
注意点:
-
条件を安全に評価するために正規表現を使用して変数を置換。
-
条件が適用できない場合を安全にスキップ。
6.2.4. process_query関数
def process_query(query, data):
pattern = r"\[\d*(:\d*)?(:\d*)?\]"
match = re.fullmatch(pattern, query)
unsupported = any(op in query for op in [":", "..", "[?"])
if match or not unsupported:
jsonpath_expr = parse(query)
result = [match.value for match in jsonpath_expr.find(data)]
return result
elif "$..*"==query:
return recursive_descent(data)
"""
Process a JSONPath query, supporting normal processing, recursive descent,
slicing, and filtering.
"""
if "$.." in query:
if "[?" in query:
# Handle filtering
key = query.split("..")[-1].split("[")[0]
condition = query.split("[?")[-1].rstrip("]")
items = flatten(recursive_descent(data, key))
return manual_filter(items, condition)
elif "[" in query and ":" in query:
# Handle slicing
key = query.split("..")[-1].split("[")[0]
slice_expr = query.split("[")[-1].rstrip("]")
start, end = (slice_expr.split(":") + [None])[:2]
start = int(start) if start else None
end = int(end) if end else None
items = flatten(recursive_descent(data, key))
return items[start:end]
elif "[" in query:
# Handle single index
key = query.split("..")[-1].split("[")[0]
index = int(query.split("[")[-1].rstrip("]"))
items = flatten(recursive_descent(data, key))
return [items[index]] if -len(items) <= index < len(items) else []
else:
# Handle recursive descent without filters
key = query.split("..")[-1].split("[")[0]
return flatten(recursive_descent(data, key))
else:
# Normal JSONPath processing
try:
jsonpath_expr = parse(query)
return [match.value for match in jsonpath_expr.find(data)]
except Exception as e:
return f"Error: {e}"
-
目的:
process_query
関数は、JSONPathクエリを受け取り、そのクエリに基づいてJSONデータを処理することを目的としています。標準的なJSONPathクエリだけでなく、再帰的な探索や条件付きフィルタリングといった高度なクエリもサポートします。 -
特徴:
-
標準クエリの直接処理:
jsonpath-ng
ライブラリがサポートするクエリ(例:$.store.book[*]
や$.store.book[0:2]
)は、直接処理されます。 -
カスタムロジックの使用:
標準ライブラリで対応できない高度なクエリ(例: 再帰的探索$..*
や条件付きフィルタリング
[?@.price<10]
)は、関数内のカスタムロジックを利用して処理します。 -
柔軟なエラーハンドリング:
標準クエリでエラーが発生した場合や、サポートされていないクエリが提供された場合でも、適切な代替ロジックを実行またはエラーメッセージを返します。
-
-
重要なロジック:
-
クエリの解析:
正規表現でクエリを解析し、標準的な構文かどうかを判定します。 -
再帰的探索の対応:
$..*
のような全要素検索クエリは、recursive_descent
関数を使用して処理します。 -
条件付きフィルタリング:
[?@.key<value]
のような条件付きクエリを動的に評価し、該当する要素を抽出します。 -
使用例:
-
クエリ:
$.store.book[*]
すべての本を取得。 -
クエリ:
$..book[?@.price<10]
価格が10未満の本を条件付きで取得。
6.3. jsonpath_ngがサポートするJSONPathの表記と基本関数の使用例
# | JSONPath Syntax | Python Example | 検索条件 |
---|---|---|---|
1 |
$.store.book[*] |
|
商店のすべての書籍 |
2 |
$.store.book[*].author |
|
商店のすべての書籍の著者 |
3 |
$..author |
|
JSON内のすべての著者 |
4 |
$..book[2] |
`parse(“$..book[2]”) |
3番目の書籍 |
5 |
$..book[-1] |
`parse(“$..book[-1]”) |
最後の書籍 |
6 |
$..book[:2] |
`parse(“$..book[:2]”) |
最初から2冊の書籍 |
7 |
$..book[1:3] |
`parse(“$..book[:2]”) |
最初から2冊めまでの書籍 |
8 |
$.store..price |
|
商店のすべての価格 |
9 |
$..book[?@.price<10] |
手動でフィルタリング: |
価格が10未満の書籍 |
8 |
$..* |
再帰的検索: |
JSON内のすべての要素 |
Test: 1. All books
Query: $.store.book[*]
Result: [
{ '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}
]
Test: 2. Authors of all books
Query: $.store.book[*].author
Result: ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
Test: 3. All authors in the document(recursive descent)
Query: $..author
Result: ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
Test: 4. The third book
Query: $..book[2]
Result: [
{ 'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99 }]
Test: 5. The last book(negative index)
Query: $..book[-1]
Result: [
{ 'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99 }
]
Test: 6. The first two books
Query: $..book[: 2]
Result: [
{ '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 }
]
Test: 7. The second and the third books
Query: $..book[1: 3]
Result: [
{ '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 }
]
Test: 8. All prices in a store
Query: $.store..price
Result: [8.95, 12.99, 8.99, 22.99, 399]
Test: 9. Filter books cheaper than 10
Query: $..book[? @.price < 10]
Result: [
{ '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 }
]
Test: 10. All elements recursively
Query: $..*
Result: [
{
' 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': 399}
},
[
{'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}
],
'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,
{'color': 'red', 'price': 399},
'red',
399
]
6.4. プログラムソース
from jsonpath_ng import parse
import re
# Sample JSON data
data = {
"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": 399},
}
}
def flatten(results):
"""Flatten nested lists into a single list."""
flat = []
for item in results:
if isinstance(item, list):
flat.extend(flatten(item))
else:
flat.append(item)
return flat
def recursive_descent(data, key=None, handle_normal=True):
"""
Traverse the JSON structure recursively to find all instances of a given key.
If no key is provided, collect all elements (for recursive wildcards like $..*).
"""
if handle_normal and key:
query = f"$..{key}" if key != "*" else "$..*"
try:
jsonpath_expr = parse(query)
result = [match.value for match in jsonpath_expr.find(data)]
return result
except Exception:
pass # Fallback to explicit handling if JSONPath fails
results = []
if isinstance(data, dict):
for k, v in data.items():
if key is None or k == key:
results.append(v)
results.extend(recursive_descent(v, key, handle_normal=False))
elif isinstance(data, list):
for item in data:
results.extend(recursive_descent(item, key, handle_normal=False))
return results
def manual_filter(data, condition):
"""
Apply a manual filter condition (e.g., @.price < 10) to a list of items.
Safely evaluates the condition for dictionary items.
"""
filtered = []
for item in data:
if isinstance(item, dict):
try:
# Replace @.key with item['key'] dynamically for safe access
condition_replaced = re.sub(r"@\.(\w+)", r'item["\1"]', condition)
if eval(condition_replaced):
filtered.append(item)
except (KeyError, AttributeError, SyntaxError):
# Skip items where the condition cannot be applied
pass
return filtered
def process_query(query, data):
pattern = r"\[\d*(:\d*)?(:\d*)?\]"
match = re.fullmatch(pattern, query)
unsupported = any(op in query for op in [":", "..", "[?"])
if match or not unsupported:
jsonpath_expr = parse(query)
result = [match.value for match in jsonpath_expr.find(data)]
return result
elif "$..*"==query:
return recursive_descent(data)
"""
Process a JSONPath query, supporting normal processing, recursive descent,
slicing, and filtering.
"""
if "$.." in query:
if "[?" in query:
# Handle filtering
key = query.split("..")[-1].split("[")[0]
condition = query.split("[?")[-1].rstrip("]")
items = flatten(recursive_descent(data, key))
return manual_filter(items, condition)
elif "[" in query and ":" in query:
# Handle slicing
key = query.split("..")[-1].split("[")[0]
slice_expr = query.split("[")[-1].rstrip("]")
start, end = (slice_expr.split(":") + [None])[:2]
start = int(start) if start else None
end = int(end) if end else None
items = flatten(recursive_descent(data, key))
return items[start:end]
elif "[" in query:
# Handle single index
key = query.split("..")[-1].split("[")[0]
index = int(query.split("[")[-1].rstrip("]"))
items = flatten(recursive_descent(data, key))
return [items[index]] if -len(items) <= index < len(items) else []
else:
# Handle recursive descent without filters
key = query.split("..")[-1].split("[")[0]
return flatten(recursive_descent(data, key))
else:
# Normal JSONPath processing
try:
jsonpath_expr = parse(query)
return [match.value for match in jsonpath_expr.find(data)]
except Exception as e:
return f"Error: {e}"
# Test cases
queries = [
{"query": "$.store.book[*]", "description": "1. All books"},
{"query": "$.store.book[*].author", "description": "2. Authors of all books"},
{"query": "$..author", "description": "3. All authors in the document (recursive descent)"},
{"query": "$..book[2]", "description": "4. The third book"},
{"query": "$..book[-1]", "description": "5. The last book (negative index)"},
{"query": "$..book[:2]", "description": "6. The first two books"},
{"query": "$..book[1:3]", "description": "7. The second and the third books"},
{"query": "$.store..price", "description": "8. All prices in a store"},
{"query": "$..book[?@.price<10]", "description": "9. Filter books cheaper than 10"},
{"query": "$..*", "description": "10. All elements recursively"},
]
for test in queries:
print(f"Test: {test['description']}")
result = process_query(test["query"], data)
print(f"Query: {test['query']}\nResult: {result}\n")
コメントを残す