Quickstart¶
This guide walks through the four operations every JSONPointer user needs: read, set, add, remove — across plain dict/list data and Pydantic models.
Construct a pointer¶
JsonPointer is a str subclass. Construct it from a raw RFC 6901 string, from a sequence of unescaped tokens, or by chaining with /:
from pydantic_jsonpointer import JsonPointer
JsonPointer("/items/0/name")
JsonPointer.from_tokens(["items", "0", "name"])
JsonPointer("/items") / "0" / "name"
All three produce the same value. Validation runs in __new__, so any of these paths will reject malformed input (RFC 6901 requires a leading /, and ~ must be followed by 0 or 1):
JsonPointer("items/0") # raises InvalidTokenError — missing leading "/"
JsonPointer("/~2") # raises InvalidTokenError — "~" must be followed by 0 or 1
From model attributes¶
Use pointer_from_model to construct pointers from Pydantic model attribute chains with automatic alias resolution:
from pydantic import BaseModel
from pydantic_jsonpointer import pointer_from_model
class Address(BaseModel):
city: str
class User(BaseModel):
name: str
address: Address
pointer_from_model(User).address.city.build() # → JsonPointer("/address/city")
Each attribute step returns a _ModelPath proxy. Call .build() (or its () alias) to finalize into a JsonPointer. The / operator also finalizes immediately.
List fields support [index] access:
class Order(BaseModel):
items: list[Address]
pointer_from_model(Order).items[0].city.build() # → JsonPointer("/items/0/city")
Read a value¶
from pydantic_jsonpointer import JsonPointer, get_value
doc = {"items": [{"name": "apple"}, {"name": "banana"}]}
get_value(doc, JsonPointer("/items/1/name"))
# "banana"
For a missing key or out-of-range index, use Ptr.try_get with a default:
from pydantic_jsonpointer import resolve
ptr = resolve(doc, JsonPointer("/items"))
ptr.try_get(default=None) # returns the list
Set / add / remove¶
from pydantic_jsonpointer import (
JsonPointer, set_value, add_value, remove_value,
)
doc = {"items": [1, 2, 3]}
set_value(doc, JsonPointer("/items/0"), 99)
# doc -> {"items": [99, 2, 3]}
add_value(doc, JsonPointer("/items/-"), 4)
# doc -> {"items": [99, 2, 3, 4]} — "-" appends to the list
add_value(doc, JsonPointer("/items/0"), 0)
# doc -> {"items": [0, 99, 2, 3, 4]} — index inserts
remove_value(doc, JsonPointer("/items/0"))
# doc -> {"items": [99, 2, 3, 4]}
The semantic difference between set and add matches RFC 6902 (JSON Patch):
setrequires the slot to already exist.addcreates a new slot (a new dict key, a new list index, a new field on an alias-aware model).
Pydantic models¶
The traversal API treats BaseModel like any other container. By default, tokens map to serialization aliases (falling back to attribute names):
from pydantic import BaseModel, Field
from pydantic_jsonpointer import JsonPointer, get_value, set_value
class Item(BaseModel):
name: str = Field(serialization_alias="itemName")
qty: int
item = Item(name="apple", qty=3)
get_value(item, JsonPointer("/itemName")) # "apple"
set_value(item, JsonPointer("/qty"), 10)
Need a different alias policy? See Field resolvers.
RootModel auto-unwrap¶
RootModel[list[…]] and RootModel[dict[…]] are transparent to the walker — pointers descend straight through .root without an explicit token:
from pydantic import RootModel
from pydantic_jsonpointer import JsonPointer, get_value, add_value
class IntList(RootModel[list[int]]):
pass
nums = IntList(root=[10, 20, 30])
get_value(nums, JsonPointer("/1")) # 20
add_value(nums, JsonPointer("/-"), 40) # nums.root -> [10, 20, 30, 40]
What's next¶
- Model-based pointers — build pointers from model attribute chains
- JsonPointer type — escape rules, Pydantic integration, the
_tokensinvariant - Traversal — the
Ptrhandle, frozen taint, mutation guards - Adapters — write an adapter for your own container type
- Field resolvers — pick the right alias policy for Pydantic models