-
-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updated Tingle.AspNetCore.JsonPatch documentation for Json Merge Patch #234
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,115 @@ | ||
# 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 | ||
{ | ||
"id": "1", | ||
"name": null, | ||
"phone": "+254722000000", | ||
"country": "ken" | ||
} | ||
``` | ||
|
||
## Remove Operation | ||
### JSON Merge Patch Example | ||
|
||
Let us set the `Name` to null and delete `orders[0]` | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Remove(x => x.Name, null) | ||
.Remove(y => y.Orders, 0); | ||
```json | ||
{ | ||
"name": "Fabrikam", | ||
"phone": "+254722000001", | ||
"country": null | ||
} | ||
``` | ||
|
||
## Replace Operation | ||
|
||
This is the same as a `Remove` operation followed by an `Add`. Let us show how to do this below: | ||
### Resource after patch | ||
|
||
```cs | ||
var order = new Order | ||
```json | ||
{ | ||
Item = "Air Fryer", | ||
Quantity = 1 | ||
}; | ||
|
||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Replace(x => x.Name, null) | ||
.Replace(y => y.Orders, order, 0); | ||
"id": "1", | ||
"name": "Fabrikam", | ||
"phone": "+254722000001", | ||
"country": null | ||
} | ||
``` | ||
|
||
The `Replace` operation can also be used to replace items in a dictionary by the given key. | ||
`id` property remains unchanged as it is not part of the merge patch request. | ||
|
||
## Move Operation | ||
### JSON Merge Patch in ASP.NET Core | ||
|
||
Let us Move `orders[1]` to before `orders[0]` and set `AlternateName` from the `Name` value. | ||
Define a `Customer` model: | ||
|
||
```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 | ||
class Customer | ||
{ | ||
public string Id { get; set; } | ||
public string Name { get; set; } | ||
public string Phone { get; set; } | ||
public string Country { get; set; } | ||
} | ||
``` | ||
|
||
## Copy Operation | ||
Add the following logic in the `Program.cs` file. This same logic can be added to `Startup.cs`. | ||
|
||
This operation is fundamentally the same as `Move` without the final `Remove` step. | ||
```cs | ||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
Let us in the example below copy the value of `Name` to the `AlternateName` and insert a copy of `orders[1]` before `orders[0]`. | ||
builder.Services.AddControllers() | ||
.AddJsonPatch() | ||
.AddJsonOptions(options => {}); // Add and configure JSON formatters | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Copy(x => x.Orders, 1, y => y.Orders, 0) | ||
.Copy(x => x.Name, y => y.AlternateName); | ||
``` | ||
var app = builder.Build(); | ||
|
||
## Test Operation | ||
app.UseHttpsRedirection(); | ||
|
||
This operation is commonly used to prevent an update when there's a concurrency conflict. | ||
app.UseAuthorization(); | ||
|
||
The following sample patch document has no effect if the initial value of `Name` is "John", because the test fails: | ||
app.MapControllers(); | ||
|
||
app.Run(); | ||
``` | ||
|
||
Use in your controller | ||
|
||
```cs | ||
var patchDoc = new JsonPatchDocument<Customer>(); | ||
patchDoc.Test(x => x.Name, "Andrew") | ||
.Add(x => x.Name, "Ben"); | ||
[HttpPatch] | ||
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. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about showing properties that remain unchanged because they are no included in the PatchMerge request body?