JsonPatchX¶
A PATCH framework for Python.
JsonPatchX starts with RFC 6902 JSON Patch and goes farther when an API needs more than a generic patch document.
If all you want is standard JSON Patch, you can use JsonPatch and stop there.
It is Pydantic-backed, standards-compliant, and intentionally easy to reach.
If PATCH is part of a public API contract, JsonPatchX gives you more structure: typed request models, validation before mutation, target-model revalidation after mutation, OpenAPI generated from the same operation models, and route-level control over which operations each endpoint accepts.
from jsonpatchx import JsonPatch, JsonPatchFor
patch = JsonPatch(
[
{"op": "replace", "path": "/status", "value": "active"},
]
)
UserPatch = JsonPatchFor[User]
That second line is the center of the project. It keeps JSON Patch on the wire, but turns the body into an explicit API contract.
Why this exists¶
RFC 6902 is good at what it set out to do. It gives you a standard document format for a sequence of patch operations and a media type for sending them over HTTP.
That still leaves a lot of practical API design questions open.
Many teams react by avoiding PATCH, or by using JSON Merge Patch when updates are simple enough that “just send the new object shape” feels easier. That is a reasonable trade-off a lot of the time. Merge Patch is a good fit for coarse object updates where array handling, explicit deletions, and per-operation semantics do not matter much.
JsonPatchX is for the cases where they do.
Those cases show up quickly in real systems. Browser clients, internal tools, third-party integrations, and increasingly LLM-generated patches all cross trust boundaries. At that point, a route usually needs more than “send me a list of patch dicts and I’ll try to apply them.”
What changes when PATCH becomes a contract¶
Once PATCH is part of an API surface, you usually care about things like:
- whether the request body is validated before mutation
- whether the patched result is validated as the target model
- whether OpenAPI matches what the route actually accepts
- whether one endpoint should accept a smaller mutation vocabulary than another
- whether repeated domain mutations should have their own named operations
- whether exact-path addressing is enough, or query-style targeting would be clearer
That is the contract layer JsonPatchX adds.
It is still JSON Patch at the bottom. The difference is that the patch layer stops being anonymous.
Why not just use Merge Patch¶
Merge Patch stays attractive because it is simple to explain and simple to generate. For some APIs, that simplicity wins.
But it comes with different trade-offs. It is much less explicit about mutation intent. It gets awkward once arrays matter. It is not a good fit when you want operation-level policy, extension, or stable error semantics around specific kinds of mutations.
JsonPatchX is not trying to replace Merge Patch everywhere. It is for APIs that want the precision of patch operations, plus a cleaner way to govern them.
You do not have to buy into the whole vision¶
The User Guide starts with plain RFC 6902 because that keeps the moving parts small. That is a learning path, not a demand.
You can use JsonPatchX in layers:
- use
JsonPatchfor plain standard JSON Patch - use
JsonPatchFor[Target]when PATCH becomes part of a FastAPI contract - use
JsonPatchFor[Target, Registry]when different endpoints or environments need different operation sets - add custom operations or
JSONSelectoronly where the domain really needs them
That progression matters. JsonPatchX should feel usable on day one even if you never adopt the whole idea.
Where this is heading¶
JSON Patch has stayed deliberately small for a long time. JSONPath now has a standard. That makes this a good moment to experiment with richer PATCH contracts without throwing away the RFC core.
JsonPatchX is meant to be a serious place to do that.
Keep standard JSON Patch easy. Keep extensions explicit. Try better targeting, better operation semantics, and better governance in production-sized systems. Let the good ideas survive.
The next page gets you running with plain JsonPatch and then the smallest
possible JsonPatchFor[...] route.