JsonPatch

Overview

JGoedde.JsonPatch is a library designed to facilitate the application of JSON Patch operations on objects in C#. This package enables you to apply operations (such as add, remove, replace, etc.) to a specific object of type T using a set of provided patch operations.

It wraps and extends the functionality of ASP.NET Core´s JsonPatchDocument<T> by providing a more flexible, easy-to-use interface to create patch operations from JSON-like input, making it easier to apply partial updates to objects in a strongly-typed manner.

Key Features

  • JSON Patch Support: Supports common JSON Patch operations (add, remove, replace) on objects.
  • Strongly Typed: Works with any class (T : class) and ensures that patch operations are applied to the correct properties.
  • Path Navigation: Allows you to navigate properties or collections (arrays) through a path expression (e.g., /property/subproperty or /array/0/subproperty).
  • Dynamic Type Handling: Can handle nested properties and collections, ensuring correct type resolution even in complex object graphs.
  • PatchObservable: Can observe changes on (readable) properties of an object

Installation

To install the package, use NuGet:

Install-Package JGoedde.JsonPatch

Or add it to your .csproj file:

<PackageReference Include="JGoedde.JsonPatch" Version="1.0.0" />

Usage

[HttpPatch("{id}")]
public async Task<IActionResult> Patch(string id,[FromBody] JsonPatch<MyObject> model)
{
    JsonPatchDocument<MyObject> patchDocument = model.CreatePatchDocument();
    // Use patchDocument...
}

Class and Method Breakdown

PatchObservable<T>

Observe changes on objects and trigger listeners with JsonPatch compliant patches

Example

// class needs to inherit from PatchObservable<self>
public class TestClassPerson:PatchObservable<TestClassPerson>
{
    public string Id{get;set;} = Guid.NewGuid().ToString();
    public string? Name { get; set; }
    public int Age { get; set; }
    public DateTime Birth { get; set; }
    public List<string>? Titles { get; set; }
    // Deep structures are possible - self-referencing is not supported, will not be detected and will result in stack-overflows
    public List<TestClassPerson>? Children { get; set; }
}

// Usage:

var person = new TestClassPerson();
// Add a listener - you can add multiple
person.Listen(async (personId,patches) => {
    // Use personId and patches
    // return false to remove/stop listener
    return true;
}, person.Id);

// In a transaction all changes on public properties will be observed - after the using all changes are combined to a patch and handled by the listeners
await using(person.Transaction()){
    person.Name = "Test";
    person.Age = 20;
    person.Birth = DateTime.Now;
    if(person.Titles == null)
        person.Titles = new List<string>();
    person.Titles.Add("Test");
}

// Alternatively you can change single properties with `Change`
await person.Change(p=>p.Name, "New Name");

// Change can also be called with an action
await person.Change(p=>{
    p.Name = "New Name";
    p.Age = 30;
});

// After awaiting the method `Change` or the transaction all listeners are handled
// Listeners are running in parallel

JsonPatch<T>

This is the main class used to handle the patch operations.

Properties:

  • Operations: A list of Operation objects that define the patch operations (add, remove, replace, etc.).

Methods:

  • CreatePatchDocument(): Converts the operations into a JsonPatchDocument<T>, which is a format understood by ASP.NET Core´s JSON Patch handler.
  • GetPropertyByPath(string path): Resolves a property on the object based on the provided JSON Patch path. It handles nested properties and array indices.

Operation

Represents a single patch operation.

Properties:

  • op: The operation type (add, remove, replace).
  • path: The path to the property to be patched.
  • from: (optional) The path from which to copy the value for the operation.
  • value: The value to set or remove from the target property.

Example: Full Usage

public class MyObject
{
    public string Name { get; set; }
    public Address Address { get; set; }
    public List<string> Tags { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

var patch = new JsonPatch<MyObject>
{
    Operations = new List<Operation>
    {
        new Operation
        {
            op = "replace",
            path = "/Name",
            value = "New Name"
        },
        new Operation
        {
            op = "replace",
            path = "/Address/Street",
            value = "New Street"
        },
        new Operation
        {
            op = "add",
            path = "/Tags/0",
            value = "Important"
        }
    }
};

var patchDoc = patch.CreatePatchDocument();
var myObject = new MyObject
{
    Name = "Old Name",
    Address = new Address { Street = "Old Street", City = "Old City" },
    Tags = new List<string> { "Old Tag" }
};

patchDoc.ApplyTo(myObject);

// myObject will now have:
// Name = "New Name"
// Address.Street = "New Street"
// Tags = ["Important", "Old Tag"]

Why Use This Package?

  • Type Safety: Ensures that the patch operations are type-safe and apply to the correct properties.
  • Flexible Patch Paths: Handles complex paths, including nested properties and array indexing, making it suitable for deep object graphs.
  • Extends JSON Patch: While ASP.NET Core´s built-in JsonPatchDocument handles patch operations, this package provides additional functionality for easier creation of patch operations from raw JSON-like data.