The typical update cycle for an API resource is to (1) GET the representation, (2) modify it and (3) PUT back the entire representation. This can waste bandwidth and processing time for large resources. An alternative is to use the HTTP PATCH extension method to only send the differences between two resources. HTTP PATCH applies a set of changes to the document referenced by the HTTP request.
PATCH /file.txt HTTP/1.1
Host: sookocheff.com
Content-Type: application/json
If-Match: "e0036bbc6f"
[description of changes]
The format of the PATCH request body differs depending on the representation of the resource. For JSON documents, JSON Patch defines this format.
A JSON Patch document is a sequential list of operations to be applied to an object. Each operation is a JSON object having exactly one op
member.
Valid operations are add
, remove
, replace
, move
, copy
and test
. Any other operation is considered an error.
{ "op": "add" }
Each operation must also have exactly one path
member.
The path
member is a JSON Pointer that determines a location within the JSON document to modify.
{ "op": "add", "path": "/player/name" }
The remaining elements of a JSON Patch operation depend on the particular operation being performed.
add
The add
operation is used in different ways depending on the target of the path
being referenced. Generally speaking we can use add
to append to a list, add a member to an object or update the value of an existing field. The add
operation accepts a value
member which is the value to update the referenced path
.
Append to a List
To append a value to a list you use an existing list as the path
of the operation. So, given the JSON document.
{
"orders": [{"id": 123}, {"id": 456}]
}
We can append an order to the list using the add
operation.
{ "op": "add", "path": "/orders", "value": {"id": 789} }
After applying the patch we get the final document.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}]
}
Add a Member to an Object
If the path
references a member of an object that does not exist, a new member is added to the object. We start with our JSON document listing our orders.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789"}]
}
Using this JSON Patch document we can add a total and a currency member to the document.
[
{ "op": "add", "path": "/total", "value": 20.00 },
{ "op": "add", "path": "/currency", "value": "USD" }
]
After applying the patch we get the final representation.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 20.00,
"currency": "USD"
}
Update an Existing Member of an Object
If the path
refers to an existing object member, that member is updated with the newly supplied value.
Given the JSON document.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 20.00,
"currency": "USD"
}
We can update the total by using an add
operation.
{ "op": "add", "path": "/total", "value": 30.00 },
Leaving the final result.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 30.00,
"currency": "USD"
}
remove
Remove is a simple operation. The target location of the path
is removed from the object.
Starting with the following document.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 30.00,
"currency": "USD"
}
We can remove the currency
member with a remove
operation.
{ "op": "remove", "path": "/currency" }
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 30.00
}
We can also remove an element from an array. All remaining elements are shifted one position to the left. To remove order 456
we can remove the array index referencing this order.
{ "op": "remove", "path": "/orders/1" }
{
"orders": [{"id": 123}, {"id": 789}],
"total": 30.00
}
replace
Replace is used to set a new value to a member of the object. It is logically equivalent to a remove
operation followed by an add
operation to the same path
or to an add
operation to an existing member.
Given the following JSON document.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 20.00,
"currency": "USD"
}
We can apply the replace
operation to update the order total.
{ "op": "replace", "path": "/total", "value": 30.00 },
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 30.00,
"currency": "USD"
}
move
The move
operation removes the value at a specified location and adds it to the target location. The removal location is given by a from
member and the target location is given by the path
member.
Given this starting document.
{
"orders": [{"id": 123}, {"id": 456}, {"id": 789}],
"total": 30.00,
"currency": "USD"
}
We can move an order to the root of the document by applying this JSON patch.
json
{ "op": "move", "from": "/orders/0", "path": "/rootOrder" }
{
"orders": [{"id": 456}, {"id": 789}],
"rootOrder": {"id": 123},
"total": 30.00,
"currency": "USD"
}
copy
copy
is like move
. It copies the value at the from
location to the path
location, leaving duplicates of the data at each location.
{ "op": "copy", "from": "/orders/0", "path": "/rootOrder" }
test
The HTTP PATCH method is atomic and the patch should only be applied if all operations can be safely applied. The test
operation can offer additional validation to ensure that patch preconditions or postconditions are met. If the test fails the whole patch is discarded. test
is strictly an equality check.
{ "op": "test", "value": 30.00, "path": "/total" }
Conclusion
JSON Patch is an effective way to provide diffs of your API resources. Most languages already have an implementation available. There is no reason not to adopt the HTTP PATCH today.