Skip to content
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 4 commits into from
Apr 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 73 additions & 89 deletions src/Tingle.AspNetCore.JsonPatch/README.md
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
Copy link
Contributor

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?

}
```

## 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.