aspnetcore/release-notes/aspnetcore-10/includes/jsonPatch.md
System.Text.JsonIn web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency.
This release introduces a new implementation of xref:Microsoft.AspNetCore.JsonPatch based on xref:System.Text.Json?displayProperty=fullName serialization. This feature:
System.Text.Json library, which is optimized for .NET.Newtonsoft.Json-based implementation.The following benchmarks compare the performance of the new System.Text.Json implementation with the legacy Newtonsoft.Json implementation.
| Scenario | Implementation | Mean | Allocated Memory |
|---|---|---|---|
| Application Benchmarks | Newtonsoft.JsonPatch | 271.924 µs | 25 KB |
| System.Text.JsonPatch | 1.584 µs | 3 KB | |
| Deserialization Benchmarks | Newtonsoft.JsonPatch | 19.261 µs | 43 KB |
| System.Text.JsonPatch | 7.917 µs | 7 KB |
These benchmarks highlight significant performance gains and reduced memory usage with the new implementation.
Notes:
To enable JSON Patch support with System.Text.Json, install the Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet package.
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
This package provides a JsonPatchDocument<T> class to represent a JSON Patch document for objects of type T and custom logic for serializing and deserializing JSON Patch documents using System.Text.Json. The key method of the JsonPatchDocument<T> class is ApplyTo, which applies the patch operations to a target object of type T.
The following examples demonstrate how to use the ApplyTo method to apply a JSON Patch document to an object.
JsonPatchDocumentThe following example demonstrates:
add, replace, and remove operations.// Original object
var person = new Person {
FirstName = "John",
LastName = "Doe",
Email = "[email protected]",
PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
Address = new Address
{
Street = "123 Main St",
City = "Anytown",
State = "TX"
}
};
// Raw JSON Patch document
var jsonPatch = """
[
{ "op": "replace", "path": "/FirstName", "value": "Jane" },
{ "op": "remove", "path": "/Email"},
{ "op": "add", "path": "/Address/ZipCode", "value": "90210" },
{
"op": "add",
"path": "/PhoneNumbers/-",
"value": { "Number": "987-654-3210", "Type": "Work" }
}
]
""";
// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);
// Apply the JSON Patch document
patchDoc!.ApplyTo(person);
// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
// Output:
// {
// "firstName": "Jane",
// "lastName": "Doe",
// "address": {
// "street": "123 Main St",
// "city": "Anytown",
// "state": "TX",
// "zipCode": "90210"
// },
// "phoneNumbers": [
// {
// "number": "123-456-7890",
// "type": "Mobile"
// },
// {
// "number": "987-654-3210",
// "type": "Work"
// }
// ]
// }
The ApplyTo method generally follows the conventions and options of System.Text.Json for processing the JsonPatchDocument, including the behavior controlled by the following options:
NumberHandling: Whether numeric properties are read from strings.PropertyNameCaseInsensitive: Whether property names are case-sensitive.Key differences between System.Text.Json and the new JsonPatchDocument<T> implementation:
ApplyTo patches.System.Text.Json deserialization relies on the declared type to identify eligible properties.JsonPatchDocument with error handlingThere are various errors that can occur when applying a JSON Patch document. For example, the target object may not have the specified property, or the value specified might be incompatible with the property type.
JSON Patch also supports the test operation. The test operation checks if a specified value is equal to the target property, and if not, returns an error.
The following example demonstrates how to handle these errors gracefully.
[!IMPORTANT] The object passed to the
ApplyTomethod is modified in place. It is the caller's responsiblity to discard these changes if any operation fails.
// Original object
var person = new Person {
FirstName = "John",
LastName = "Doe",
Email = "[email protected]"
};
// Raw JSON Patch document
var jsonPatch = """
[
{ "op": "replace", "path": "/Email", "value": "[email protected]"},
{ "op": "test", "path": "/FirstName", "value": "Jane" },
{ "op": "replace", "path": "/LastName", "value": "Smith" }
]
""";
// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);
// Apply the JSON Patch document, catching any errors
Dictionary<string, string[]>? errors = null;
patchDoc!.ApplyTo(person, jsonPatchError =>
{
errors ??= new ();
var key = jsonPatchError.AffectedObject.GetType().Name;
if (!errors.ContainsKey(key))
{
errors.Add(key, new string[] { });
}
errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray();
});
if (errors != null)
{
// Print the errors
foreach (var error in errors)
{
Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}");
}
}
// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
// Output:
// Error in Person: The current value 'John' at path 'FirstName' is not equal
// to the test value 'Jane'.
// {
// "firstName": "John",
// "lastName": "Smith", <<< Modified!
// "email": "[email protected]", <<< Modified!
// "phoneNumbers": []
// }
When using the Microsoft.AspNetCore.JsonPatch.SystemTextJson package, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package.
[!IMPORTANT] This is not an exhaustive list of threats. App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection.
By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks.
Consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks, including:
copy operation that duplicates large object graphs multiple times, leading to excessive memory consumption.ApplyTo.public void Validate(JsonPatchDocument<T> patch)
{
// This is just an example. It's up to the developer to make sure that
// this case is handled properly, based on the app's requirements.
if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count()
> MaxCopyOperationsCount)
{
throw new InvalidOperationException();
}
}