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 2 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
157 changes: 68 additions & 89 deletions src/Tingle.AspNetCore.JsonPatch/README.md
Original file line number Diff line number Diff line change
@@ -1,131 +1,110 @@
# 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
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?

}
```

```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`.
Copy link
Contributor

@mburumaxwell mburumaxwell Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using casing on the file names that is popularly used?


```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]
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.