Something that is becoming more common in HTTP APIs is the ability to perform partial updates to resources rather than replacing the entire resource with a PUT request.
JSON Patch is a format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed. When used in combination with the HTTP PATCH method it allows partial updates for HTTP APIs in a standards compliant way.
The specification defines the following operations:
- Add - Adds a value to an object or inserts it into an array
- Remove - Removes a value from an object or array
- Replace - Replaces a value. Equivalent to a “remove” followed by an “add”
- Copy - Copy a value from one location to another within the JSON document. Both from and path are JSON Pointers.
- Move - Move a value from one location to the other. Both from and path are JSON Pointers.
- Test - Tests that the specified value is set in the document. If the test fails then the patch as a whole should not apply.
Patching resources in ASP.NET Core
A relatively undocumented feature in ASP.NET Core is the support for JSON Patch which I found whilst browsing through the ASP.NET repos on GitHub.
Everything other than the “test” operation is supported. Given that C# is a static language we do get slightly different behaviour depending on the object being patched. For example, a “remove” operation will not actually remove the property from an instance of a C# class.
Getting started
To get started we’ll add a reference to Microsoft.AspNet.JsonPatch
in project.json
:
"dependencies": {
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
"Microsoft.AspNet.JsonPatch": "1.0.0-rc1-final",
},
To test out the various Patch operations I’m going to use the following class:
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public List<string> Links { get; set; }
}
This will allow us to test patch operations against properties and arrays.
I then created a simple HTTP API that accepts a patch in the form of a JsonPatchDocument
(part of Microsoft.AspNet.JsonPatch) and applies it to an existing contact. We then return the original and patched versions so we can view what has changed:
[Route("api/[controller]")]
public class ContactsController : Controller
{
private Contact contact = new Contact
{
FirstName = "Ben",
LastName = "Foster",
Age = 30,
Links = new List<string> { "http://benfoster.io" }
};
[HttpPatch]
public IActionResult Patch([FromBody]JsonPatchDocument<Contact> patch)
{
var patched = contact.Copy();
patch.ApplyTo(patched, ModelState);
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var model = new
{
original = contact,
patched = patched
};
return Ok(model);
}
}
With our API ready we can now test various JSON Patch operations by sending PATCH requests to /api/contacts
.
Setting property values
To set a property value we send a replace operation. Note that we always send an array of operations even if you’re only sending a single operation. Let’s update the FirstName
and Age
properties:
PATCH /api/contacts
[
{
"op": "replace",
"path": "/firstname",
"value": "Benjamin"
},
{
"op": "replace",
"path": "/age",
"value": "29"
},
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Benjamin",
"LastName": "Foster",
"Age": 29,
"Links": [
"http://benfoster.io"
]
}
}
You can see that the patched contact has had it’s FirstName
and Age
properties updated.
If you look back at the code for the controller you’ll see we’re passing the ModelState
to the patch document. This will populate ModelState
with any patch errors. For example, let’s try and replace a property that does not exist:
PATCH /api/contacts
[
{
"op": "replace",
"path": "/foo",
"value": "Bar"
}
]
Response:
{
"Contact": [
"The property at path '/foo' could not be removed."
]
}
Resetting property values
To reset a property value we use the remove operation. Again this is due to the fact that we can’t actually remove the property from the C# class definition so instead the ASP.NET Core implementation will set a property back to it’s default value i.e. default(T)
.
PATCH /api/contacts
[
{
"op": "remove",
"path": "/lastname"
},
{
"op": "remove",
"path": "/age"
},
{
"op": "remove",
"path": "/links"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": null,
"Age": 0,
"Links": null
}
}
Adding items to an array
Suppose I want to add additional links to my contact. To add items to an array we use the add operation and include the index of the item in the path
. To add an item to the end of the array use the -
operator:
PATCH /api/contacts
[
{
"op": "add",
"path": "/links/-",
"value": "http://twitter.com/benfosterdev"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io",
"http://twitter.com/benfosterdev"
]
}
}
If we wanted to add a link to the beginning of the array instead we can specify an index:
PATCH /api/contacts
[
{
"op": "add",
"path": "/links/0",
"value": "http://twitter.com/benfosterdev"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://twitter.com/benfosterdev",
"http://benfoster.io"
]
}
}
Note that if we provide an index that does not exist we’ll get an error:
PATCH /api/contacts
[
{
"op": "add",
"path": "/links/5",
"value": "http://twitter.com/benfosterdev"
}
]
Response:
{
"Contact": [
"For operation 'add' on array property at path '/links/5', the index is larger than the array size."
]
}
Removing items from an array
To remove items from an array we use the remove operation and similar to above provide the index of the item we want to remove. Let’s remove the first link from our contact:
PATCH /api/contacts
[
{
"op": "remove",
"path": "/links/0"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": []
}
}
Replacing items in an array
Suppose instead of just removing the first link from our contact we wanted to replace it. This time we use the replace operation and provide the path of the item to replace:
PATCH /api/contacts
[
{
"op": "replace",
"path": "/links/0",
"value": "http://github.com/benfoster"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://github.com/benfoster"
]
}
}
Moving items in an array
Finally, let’s test out the move operation to, you guessed it, move items in an array. This time we specify the from
path of the item we wish to move and the path
where it should be moved to.
PATCH:
[
{
"op": "add",
"path": "/links/-",
"value": "http://twitter.com/benfosterdev"
},
{
"op": "add",
"path": "/links/-",
"value": "http://github.com/benfoster"
},
{
"op": "move",
"from": "/links/2",
"path": "/links/0"
}
]
Response:
{
"original": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://benfoster.io"
]
},
"patched": {
"FirstName": "Ben",
"LastName": "Foster",
"Age": 30,
"Links": [
"http://github.com/benfoster",
"http://benfoster.io",
"http://twitter.com/benfosterdev"
]
}
}
So what happened here? Well first we added two new links by providing an add operation and then we moved the last link (the GitHub one) to the beginning of the array with a move operation.
Patching dynamic values
One nice feature of the JSON Patch library is that it also supports C# dynamic objects making it possible to use add/remove operations to add or remove properties. It even supports nested objects:
[HttpPatch("dynamic")]
public IActionResult Patch([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
Let’s try and recreate our contact using JSON Patch operations:
PATCH /api/contacts/dynamic
[
{
"op": "add",
"path": "/FirstName",
"value": "Ben"
},
{
"op": "add",
"path": "/LastName",
"value": "Ben"
},
{
"op": "add",
"path": "/Links",
"value": [{
"url": "http://benfoster.io",
"title": "Blog"
}]
},
{
"op": "add",
"path": "/Links/-",
"value": [{
"url": "http://twitter.com/benfosterdev",
"title": "Twitter"
}]
},
]
Response:
{
"FirstName": "Ben",
"LastName": "Ben",
"Links": [
{
"url": "http://benfoster.io",
"title": "Blog"
},
[
{
"url": "http://twitter.com/benfosterdev",
"title": "Twitter"
}
]
]
}
Wrapping up
Use the ASP.NET Core Json Patch library to support partial updates (patches) in your APIs using JSON Patch operations.
Nancy Support
Shortly after publishing this post, Nancy core contributor and evangelist Jonathan Channon added support for the ASP.NET Core JSON Patch library to Nancy. He moves fast, real fast.