-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updated Tingle.AspNetCore.JsonPatch documentation for Json Merge Patch
- Loading branch information
1 parent
05df29a
commit b48922f
Showing
1 changed file
with
69 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,131 +1,111 @@ | ||
# Tingle.AspNetCore.JsonPatch | ||
|
||
The primary goal of this library is to provide functionalities to perform [JsonPatch](https://tools.ietf.org/html/rfc6902) operations on documents using `System.Text.Json` library. We'll show how to do this in some examples below. | ||
The primary goal of this library is to provide functionalities to perform [Json Patch](https://datatracker.ietf.org/doc/html/rfc6902) and [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) operations on documents using `System.Text.Json` library. | ||
|
||
Let us first define a class representing a customer with orders. | ||
## Json Patch | ||
|
||
JSON patch support is quite similar to Microsoft's equivalent for [Newtonsoft.Json](https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-8.0). The only difference is how you configure the input and output formatters for all JSON content. This should be done as shown below: | ||
|
||
```cs | ||
class Customer | ||
{ | ||
public string Name { get; set; } | ||
public List<Order> Orders { get; set; } | ||
public string AlternateName { get; set; } | ||
} | ||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
class Order | ||
{ | ||
public string Item { get; set; } | ||
public int Quantity { get; set;} | ||
} | ||
``` | ||
builder.Services.AddControllers() | ||
.AddJsonPatch() // would be .AddNewtonsoftJson() in the Newtonsoft.Json equivalent | ||
.AddJsonOptions(options => {}); // Add and configure JSON formatters | ||
An instantiated `Customer` object would then look like this: | ||
var app = builder.Build(); | ||
|
||
```cs | ||
{ | ||
"name": "John", | ||
"alternateName": null, | ||
"orders": | ||
[ | ||
{ | ||
"item": "Toy car", | ||
"quantity": 1 | ||
}, | ||
{ | ||
"item": "C# In A Nutshell Book", | ||
"quantity": 1 | ||
} | ||
] | ||
} | ||
``` | ||
app.UseHttpsRedirection(); | ||
|
||
A JSON Patch document has an array of operations. Each operation identifies a particular type of change. Examples of such changes include adding an array element or replacing a property value. | ||
app.UseAuthorization(); | ||
|
||
Let us create `JsonPatchDocument<Customer>` instance to demonstrate the various patching functionalities we provide. | ||
app.MapControllers(); | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
// Define operations here... | ||
app.Run(); | ||
``` | ||
|
||
By default, the case transform type is `LowerCase`. Other options available are `UpperCase`, `CamelCase` and `OriginalCase`. These can be set via the constructor of the `JsonPatchDocument<T>`. For our example purposes we'll go with the default casing. Now let us see the supported patch operations. | ||
## Json Merge Patch | ||
|
||
## Add Operation | ||
The library helps to deserialize HTTP requests' and responses' JSON body content for merge patch operation. If the merge patch request contains members that appear as null on the target object, those members are added. If the target object contains the member, the value is replaced. Members with null values in the merge patch requests, are removed from the target object (set to null or default). | ||
|
||
Let us set the `Name` of the customer and add an object to the end of the `orders` array. | ||
For example, the following JSON documents represent a resource, a JSON Merge Patch document for the resource, and the result of applying the Patch operations. | ||
|
||
```cs | ||
var order = new Order | ||
{ | ||
Item = "Car tracker", | ||
Quantity = 10 | ||
}; | ||
### Resource Example | ||
|
||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Add(x => x.Name, "Ben") | ||
.Add(y => y.Orders, order); | ||
```json | ||
{ | ||
"name": null, | ||
"phone": "+254722000000", | ||
"country": "ken" | ||
} | ||
``` | ||
|
||
## Remove Operation | ||
### JSON Merge Patch Example | ||
|
||
Let us set the `Name` to null and delete `orders[0]` | ||
```json | ||
{ | ||
"name": "Fabrikam", | ||
"phone": "+254722000001", | ||
"country": null | ||
} | ||
``` | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Remove(x => x.Name, null) | ||
.Remove(y => y.Orders, 0); | ||
### Resource after patch | ||
|
||
```json | ||
{ | ||
"name": "Fabrikam", | ||
"phone": "+254722000001", | ||
"country": null | ||
} | ||
``` | ||
|
||
## Replace Operation | ||
### JSON Merge Patch in ASP.NET Core | ||
|
||
This is the same as a `Remove` operation followed by an `Add`. Let us show how to do this below: | ||
Define a `Customer` model: | ||
|
||
```cs | ||
var order = new Order | ||
class Customer | ||
{ | ||
Item = "Air Fryer", | ||
Quantity = 1 | ||
}; | ||
|
||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Replace(x => x.Name, null) | ||
.Replace(y => y.Orders, order, 0); | ||
public string Name { get; set; } | ||
public string Phone { get; set; } | ||
public string Country { get; set; } | ||
} | ||
``` | ||
|
||
The `Replace` operation can also be used to replace items in a dictionary by the given key. | ||
|
||
## Move Operation | ||
|
||
Let us Move `orders[1]` to before `orders[0]` and set `AlternateName` from the `Name` value. | ||
Add the following logic in the `program.cs` file. This same logic can be added to `startup.cs`. | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Move(x => x.Orders, 0, y => y.Orders, 1) // swap the orders | ||
.Move(x => x.Name, y => y.AlternateName); // set AlternateName to Name while leaving Name as null | ||
``` | ||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
## Copy Operation | ||
builder.Services.AddControllers() | ||
.AddJsonPatch() | ||
.AddJsonOptions(options => {}); // Add and configure JSON formatters | ||
This operation is fundamentally the same as `Move` without the final `Remove` step. | ||
var app = builder.Build(); | ||
|
||
Let us in the example below copy the value of `Name` to the `AlternateName` and insert a copy of `orders[1]` before `orders[0]`. | ||
app.UseHttpsRedirection(); | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Copy(x => x.Orders, 1, y => y.Orders, 0) | ||
.Copy(x => x.Name, y => y.AlternateName); | ||
``` | ||
app.UseAuthorization(); | ||
|
||
## Test Operation | ||
app.MapControllers(); | ||
|
||
This operation is commonly used to prevent an update when there's a concurrency conflict. | ||
app.Run(); | ||
``` | ||
|
||
The following sample patch document has no effect if the initial value of `Name` is "John", because the test fails: | ||
Use in your controller | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Test(x => x.Name, "Andrew") | ||
.Add(x => x.Name, "Ben"); | ||
[HttpPatch] | ||
[Consumes(JsonMergePatchDocument.ContentType)] | ||
public void Patch([FromBody] JsonPatchMergeDocument<Customer> patch) | ||
{ | ||
... | ||
patch.ApplyTo(customer, ModelState); | ||
... | ||
} | ||
``` | ||
|
||
The instantiated patch document can then be serialized or deserialized using the `JsonSerializer` in the `System.Text.Json` library. | ||
In a real app, the code would retrieve the data from a store such as a database and update the database after applying the patch. | ||
|
||
The preceding action method example calls an overload of `ApplyTo` that takes model state as one of its parameters. With this option, you can get error messages in responses. |