ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.