Skip to content

Pointer Backends

This module contains the pointer backend protocol and related backend-level utilities used by JSONPointer.

DEFAULT_POINTER_CLS

Default JSON Pointer backend powered by jsonpointer.JsonPointer.

This implementation parses RFC 6901 pointer strings, exposes unescaped parts, reconstructs canonical pointers from parts, and resolves pointers against JSON documents.

Source code in jsonpatchx/backend.py
class DEFAULT_POINTER_CLS:
    """
    Default JSON Pointer backend powered by `jsonpointer.JsonPointer`.

    This implementation parses RFC 6901 pointer strings, exposes unescaped
    parts, reconstructs canonical pointers from parts, and resolves pointers
    against JSON documents.
    """

    __slots__ = ("_parts", "_pointer")

    @override
    def __init__(self, pointer: str) -> None:
        """Parse an RFC 6901 pointer string and cache its unescaped parts.

        Arguments:
            pointer: RFC 6901 pointer string to parse.
        """
        self._pointer = _FixedJsonPointer(pointer)
        self._parts = cast(Sequence[str], self._pointer.parts)

    @property
    def parts(self) -> Sequence[str]:
        """Return the pointer's unescaped RFC 6901 reference tokens.

        Returns:
            The pointer's unescaped reference tokens.
        """
        return self._parts

    @classmethod
    def from_parts(cls, parts: Iterable[str]) -> Self:
        """Build a canonical RFC 6901 pointer from unescaped reference tokens.

        Arguments:
            parts: Unescaped RFC 6901 reference tokens.

        Returns:
            A canonical RFC 6901 pointer for those tokens.
        """
        canonical = _FixedJsonPointer.from_parts(parts)
        return cls(str(canonical))

    def resolve(self, doc: JSONValue) -> JSONValue:
        """Resolve this pointer against a JSON document.

        Arguments:
            doc: JSON document to resolve against.

        Returns:
            The value targeted by this pointer.
        """
        return cast(JSONValue, self._pointer.resolve(doc))

    @override
    def __str__(self) -> str:
        """Return the canonical RFC 6901 string form.

        Returns:
            The canonical RFC 6901 string representation.
        """
        return str(self._pointer)

    @override
    def __repr__(self) -> str:
        """Return a debugging representation of this backend instance.

        Returns:
            A representation showing the backend name and source pointer.
        """
        return "JsonPointerRFC6901(" + repr(self._pointer.path) + ")"

parts property

Return the pointer's unescaped RFC 6901 reference tokens.

Returns:

Type Description
Sequence[str]

The pointer's unescaped reference tokens.

from_parts(parts) classmethod

Build a canonical RFC 6901 pointer from unescaped reference tokens.

Parameters:

Name Type Description Default
parts Iterable[str]

Unescaped RFC 6901 reference tokens.

required

Returns:

Type Description
Self

A canonical RFC 6901 pointer for those tokens.

Source code in jsonpatchx/backend.py
@classmethod
def from_parts(cls, parts: Iterable[str]) -> Self:
    """Build a canonical RFC 6901 pointer from unescaped reference tokens.

    Arguments:
        parts: Unescaped RFC 6901 reference tokens.

    Returns:
        A canonical RFC 6901 pointer for those tokens.
    """
    canonical = _FixedJsonPointer.from_parts(parts)
    return cls(str(canonical))

resolve(doc)

Resolve this pointer against a JSON document.

Parameters:

Name Type Description Default
doc JSONValue

JSON document to resolve against.

required

Returns:

Type Description
JSONValue

The value targeted by this pointer.

Source code in jsonpatchx/backend.py
def resolve(self, doc: JSONValue) -> JSONValue:
    """Resolve this pointer against a JSON document.

    Arguments:
        doc: JSON document to resolve against.

    Returns:
        The value targeted by this pointer.
    """
    return cast(JSONValue, self._pointer.resolve(doc))

DEFAULT_SELECTOR_CLS

Default JSONPath selector backend powered by python-jsonpath.

This implementation compiles JSONPath expressions with the shared strict environment and yields exact pointer locations for each match.

Disclaimer

This backend follows RFC 9535 path syntax and semantics, except on Python 3.14 and later where regex-related behavior falls back to Python's built-in re module because the upstream iregexp-check dependency is not yet compatible with free-threaded Python.

Source code in jsonpatchx/backend.py
class DEFAULT_SELECTOR_CLS:
    """
    Default JSONPath selector backend powered by `python-jsonpath`.

    This implementation compiles JSONPath expressions with the shared strict
    environment and yields exact pointer locations for each match.

    Disclaimer:
        This backend follows RFC 9535 path syntax and semantics, except on
        Python 3.14 and later where regex-related behavior falls back to
        Python's built-in `re` module because the upstream `iregexp-check`
        dependency is not yet compatible with free-threaded Python.
    """

    __slots__ = ("_path", "_selector")

    def __init__(self, selector: str) -> None:
        """Compile a JSONPath selector string with the built-in strict environment.

        Arguments:
            selector: JSONPath selector string to compile.
        """
        self._path = selector
        self._selector = _DEFAULT_SELECTOR_ENV.compile(selector)

    def pointers(self, doc: JSONValue) -> Iterable[DEFAULT_POINTER_CLS]:
        """Yield canonical RFC 6901 pointers for each matched location.

        Arguments:
            doc: JSON document to evaluate against.

        Returns:
            An iterable of canonical RFC 6901 pointers for each match.
        """
        for match in self._selector.finditer(doc):
            yield DEFAULT_POINTER_CLS.from_parts((str(part) for part in match.parts))

    @override
    def __str__(self) -> str:
        """Return the selector's original source string.

        Returns:
            The original selector source string.
        """
        return self._path

    @override
    def __repr__(self) -> str:
        """Return a debugging representation of this backend instance.

        Returns:
            A representation showing the backend name and source selector.
        """
        return "JsonPathRFC9535(" + repr(self._path) + ")"

pointers(doc)

Yield canonical RFC 6901 pointers for each matched location.

Parameters:

Name Type Description Default
doc JSONValue

JSON document to evaluate against.

required

Returns:

Type Description
Iterable[DEFAULT_POINTER_CLS]

An iterable of canonical RFC 6901 pointers for each match.

Source code in jsonpatchx/backend.py
def pointers(self, doc: JSONValue) -> Iterable[DEFAULT_POINTER_CLS]:
    """Yield canonical RFC 6901 pointers for each matched location.

    Arguments:
        doc: JSON document to evaluate against.

    Returns:
        An iterable of canonical RFC 6901 pointers for each match.
    """
    for match in self._selector.finditer(doc):
        yield DEFAULT_POINTER_CLS.from_parts((str(part) for part in match.parts))

PointerBackend

Bases: Protocol

Protocol for pointer implementations used by JSONPointer.

A pointer backend parses a pointer string, exposes unescaped parts, reconstructs itself from those parts, resolves against a JSON document, and round-trips through str().

Required Invariants
  • str(type(ptr)(str(ptr))) == str(ptr)
  • str(type(ptr).from_parts(ptr.parts)) == str(ptr)

Backends define their own syntax and root representation. Higher-level JsonPatchX APIs normalize backend-raised errors, and backend instances should be safe to reuse across calls.

Source code in jsonpatchx/backend.py
@runtime_checkable
class PointerBackend(Protocol):
    """
    Protocol for pointer implementations used by `JSONPointer`.

    A pointer backend parses a pointer string, exposes unescaped `parts`,
    reconstructs itself from those parts, resolves against a JSON document, and
    round-trips through `str()`.

    Required Invariants:
        - `str(type(ptr)(str(ptr))) == str(ptr)`
        - `str(type(ptr).from_parts(ptr.parts)) == str(ptr)`

        Backends define their own syntax and root representation. Higher-level
        JsonPatchX APIs normalize backend-raised errors, and backend instances
        should be safe to reuse across calls.
    """

    @abstractmethod
    def __init__(self, pointer: str) -> None:
        """Parse a backend-specific pointer string.

        Arguments:
            pointer: Pointer string in the backend's syntax.
        """

    @classmethod
    @abstractmethod
    def from_parts(cls, parts: Iterable[str]) -> Self:
        """Build a pointer from unescaped parts.

        Arguments:
            parts: Unescaped pointer parts in traversal order.

        Returns:
            A pointer equivalent to those parts.
        """

    @abstractmethod
    def resolve(self, doc: JSONValue) -> JSONValue:
        """Resolve this pointer against a JSON document.

        Arguments:
            doc: JSON document to resolve against.

        Returns:
            The value targeted by the pointer.
        """

    @override
    @abstractmethod
    def __str__(self) -> str:
        """Return the backend's canonical string form.

        Returns:
            The canonical string representation of this pointer.
        """

    @property
    @abstractmethod
    def parts(self) -> Sequence[str]:
        """Return unescaped pointer parts.

        Returns:
            The pointer's unescaped parts in traversal order.
        """

parts abstractmethod property

Return unescaped pointer parts.

Returns:

Type Description
Sequence[str]

The pointer's unescaped parts in traversal order.

from_parts(parts) abstractmethod classmethod

Build a pointer from unescaped parts.

Parameters:

Name Type Description Default
parts Iterable[str]

Unescaped pointer parts in traversal order.

required

Returns:

Type Description
Self

A pointer equivalent to those parts.

Source code in jsonpatchx/backend.py
@classmethod
@abstractmethod
def from_parts(cls, parts: Iterable[str]) -> Self:
    """Build a pointer from unescaped parts.

    Arguments:
        parts: Unescaped pointer parts in traversal order.

    Returns:
        A pointer equivalent to those parts.
    """

resolve(doc) abstractmethod

Resolve this pointer against a JSON document.

Parameters:

Name Type Description Default
doc JSONValue

JSON document to resolve against.

required

Returns:

Type Description
JSONValue

The value targeted by the pointer.

Source code in jsonpatchx/backend.py
@abstractmethod
def resolve(self, doc: JSONValue) -> JSONValue:
    """Resolve this pointer against a JSON document.

    Arguments:
        doc: JSON document to resolve against.

    Returns:
        The value targeted by the pointer.
    """

SelectorBackend

Bases: Protocol

Protocol for custom query selector backends.

A selector backend is the query analogue of PointerBackend: it parses a selector string and can iterate exact matched pointers against a JSON document.

Required invariants
  • str(type(sel)(str(sel))) == str(sel)
Source code in jsonpatchx/backend.py
@runtime_checkable
class SelectorBackend(Protocol):
    """
    Protocol for custom query selector backends.

    A selector backend is the query analogue of `PointerBackend`: it parses a
    selector string and can iterate exact matched pointers against a JSON
    document.

    Required invariants:
        - `str(type(sel)(str(sel))) == str(sel)`
    """

    @abstractmethod
    def __init__(self, selector: str) -> None:
        """Parse and construct a backend-specific selector.

        Arguments:
            selector: Selector string in the backend's syntax.
        """

    @abstractmethod
    def pointers(self, doc: JSONValue) -> Iterable[PointerBackend]:
        """Yield exact matched pointers against a document.

        Arguments:
            doc: JSON document to evaluate against.

        Returns:
            An iterable of exact matched pointers.
        """

    @abstractmethod
    @override
    def __str__(self) -> str:
        """Return the backend's canonical string form.

        Returns:
            The canonical string representation of this selector.
        """

pointers(doc) abstractmethod

Yield exact matched pointers against a document.

Parameters:

Name Type Description Default
doc JSONValue

JSON document to evaluate against.

required

Returns:

Type Description
Iterable[PointerBackend]

An iterable of exact matched pointers.

Source code in jsonpatchx/backend.py
@abstractmethod
def pointers(self, doc: JSONValue) -> Iterable[PointerBackend]:
    """Yield exact matched pointers against a document.

    Arguments:
        doc: JSON document to evaluate against.

    Returns:
        An iterable of exact matched pointers.
    """

TargetState

Bases: Enum

Resolution state for applying a pointer-like operation to a document.

Values
  • MISSING: The root pointer targets a missing document.
  • ROOT: The pointer targets the document root.
  • PARENT_NOT_FOUND: A parent pointer segment could not be resolved.
  • PARENT_NOT_CONTAINER: The parent resolved, but is neither an object nor an array.
  • OBJECT_KEY_MISSING: The parent is an object, and the final key is not present.
  • ARRAY_KEY_INVALID: The parent is an array, and the final token is not a valid array index or append token.
  • ARRAY_INDEX_OUT_OF_RANGE: The parent is an array, and the numeric index is outside the accepted range.
  • ARRAY_INDEX_AT_END: The parent is an array, and the numeric index is exactly len(array).
  • ARRAY_INDEX_APPEND: The parent is an array, and the final token is "-".
  • VALUE_PRESENT: The pointer names an existing value.
  • VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX: The pointer names an existing array element through a negative index.
Source code in jsonpatchx/backend.py
class TargetState(Enum):
    """
    Resolution state for applying a pointer-like operation to a document.

    Values:
        - `MISSING`: The root pointer targets a missing document.
        - `ROOT`: The pointer targets the document root.
        - `PARENT_NOT_FOUND`: A parent pointer segment could not be resolved.
        - `PARENT_NOT_CONTAINER`: The parent resolved, but is neither an object nor
        an array.
        - `OBJECT_KEY_MISSING`: The parent is an object, and the final key is not
        present.
        - `ARRAY_KEY_INVALID`: The parent is an array, and the final token is not a
        valid array index or append token.
        - `ARRAY_INDEX_OUT_OF_RANGE`: The parent is an array, and the numeric index
        is outside the accepted range.
        - `ARRAY_INDEX_AT_END`: The parent is an array, and the numeric index is
        exactly `len(array)`.
        - `ARRAY_INDEX_APPEND`: The parent is an array, and the final token is
        `"-"`.
        - `VALUE_PRESENT`: The pointer names an existing value.
        - `VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX`: The pointer names an existing
        array element through a negative index.
    """

    MISSING = auto()
    ROOT = auto()
    PARENT_NOT_FOUND = auto()
    PARENT_NOT_CONTAINER = auto()
    OBJECT_KEY_MISSING = auto()
    ARRAY_KEY_INVALID = auto()
    ARRAY_INDEX_OUT_OF_RANGE = auto()
    ARRAY_INDEX_AT_END = auto()
    ARRAY_INDEX_APPEND = auto()
    VALUE_PRESENT = auto()
    VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX = auto()

classify_state(ptr, doc)

Classify how a pointer relates to a document without mutating it.

Useful for determining the applicability of an operation before attempting to apply it, and for generating informative error messages on failure.

Parameters:

Name Type Description Default
ptr PointerBackend

Pointer to classify.

required
doc JSONValue

Document to classify the pointer against.

required

Returns:

Type Description
TargetState

The resolution state that best describes how ptr relates to doc.

Source code in jsonpatchx/backend.py
def classify_state(ptr: PointerBackend, doc: JSONValue) -> TargetState:
    """
    Classify how a pointer relates to a document without mutating it.

    Useful for determining the applicability of an operation before attempting
    to apply it, and for generating informative error messages on failure.

    Arguments:
        ptr: Pointer to classify.
        doc: Document to classify the pointer against.

    Returns:
        The resolution state that best describes how `ptr` relates to `doc`.
    """
    if _is_root_ptr(ptr, doc):
        if doc is MISSING:  # type: ignore[comparison-overlap]
            return TargetState.MISSING
        return TargetState.ROOT

    try:
        parent_ptr = _parent_ptr_of(ptr)
        container = parent_ptr.resolve(doc)
        token = ptr.parts[-1]
    except Exception:
        return TargetState.PARENT_NOT_FOUND  # resolution failed to complete
    if not _is_container(container):
        return TargetState.PARENT_NOT_CONTAINER

    if _is_object(container):
        key = token
        if key not in container:
            return TargetState.OBJECT_KEY_MISSING
        return TargetState.VALUE_PRESENT

    elif _is_array(container):
        if token == "-":
            return TargetState.ARRAY_INDEX_APPEND
        if _INTEGER_ARRAY_INDEX_PATTERN.fullmatch(token):
            index = int(token)
            if index > len(container) or index < -len(container):
                return TargetState.ARRAY_INDEX_OUT_OF_RANGE
            if index == len(container):
                return TargetState.ARRAY_INDEX_AT_END
            if index < 0:
                return TargetState.VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX
            return TargetState.VALUE_PRESENT
        return TargetState.ARRAY_KEY_INVALID

    else:  # pragma: no cover
        assert_never(container)