jsonpath-ng queries, full paths and in-place updates: working versions (tested 2026-10-18)
Query nested JSON with the ext filter parser, resolve the full path of every match, then update or drop nodes in place with jsonpath-ng.
passed6 passed · 0 failed2026-10-18 03:57 UTC- Status
- passed
- Last passed
- 2026-10-18 03:57 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
jsonpath-ng@1.11.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 39095370682
The code
Exactly the files that passed. Your agent can pull them with get_recipe("jsonpath-ng-query-update") (install), or see them on GitHub.
requirements.txt
jsonpath-ng==1.11.0 pytest==9.1.1
catalog.py
from typing import Any, Callable
import jsonpath_ng.ext as jp
CATALOG: dict[str, Any] = {
"store": {
"book": [
{"title": "Refactoring", "price": 30, "tags": ["code"]},
{"title": "DDIA", "price": 45, "tags": ["code", "data"]},
{"title": "Novel", "price": 10},
],
"bicycle": {"color": "red", "price": 120},
},
"users": [
{"name": "ada", "active": True},
{"name": "bob", "active": False},
],
}
def query(data: Any, expr: str) -> list[Any]:
return [match.value for match in jp.parse(expr).find(data)]
def paths(data: Any, expr: str) -> list[str]:
return [str(match.full_path) for match in jp.parse(expr).find(data)]
def transform(data: Any, expr: str, fn: Callable[[Any, Any, str], Any]) -> Any:
# update callbacks receive (value, parent, field), not just the value
jp.parse(expr).update(data, fn)
return data
def drop_where(data: Any, expr: str, predicate: Callable[[Any], bool]) -> Any:
# filter mutates the container in place and removes matching entries
jp.parse(expr).filter(predicate, data)
return data
def set_default(data: Any, expr: str, value: Any) -> Any:
jp.parse(expr).update_or_create(data, value)
return data
tests/test_catalog.py
import copy
from catalog import CATALOG, drop_where, paths, query, set_default, transform
def test_truthy_filter_matches_all():
got = [u["name"] for u in query(CATALOG, "$.users[?(@.active)]")]
assert got == ["ada", "bob"]
got = [u["name"] for u in query(CATALOG, "$.users[?(@.active == true)]")]
assert got == ["ada"]
def test_filter_and_child_selection():
assert query(CATALOG, "$.store.book[?(@.price < 40)].title") == ["Refactoring", "Novel"]
def test_recursive_descent_full_paths():
assert paths(CATALOG, "$..price") == [
"store.book.[0].price",
"store.book.[1].price",
"store.book.[2].price",
"store.bicycle.price",
]
def test_update_callback_receives_parent_and_field():
data = copy.deepcopy(CATALOG)
transform(data, "$.store.book[*].price", lambda value, parent, field: value + 1)
assert query(data, "$.store.book[*].price") == [31, 46, 11]
assert CATALOG["store"]["book"][0]["price"] == 30
def test_update_missing_path_is_noop_but_update_or_create_builds():
data = {}
transform(data, "$.meta.owner", lambda value, parent, field: value)
assert data == {}
set_default(data, "$.meta.owner", "ops")
assert data == {"meta": {"owner": "ops"}}
def test_filter_removes_entries_in_place():
data = copy.deepcopy(CATALOG)
drop_where(data, "$.store.book[*]", lambda book: book["price"] < 20)
assert [b["title"] for b in data["store"]["book"]] == ["Refactoring", "DDIA"]
pytest.ini
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.