JsonMergePatch and .NET6

In a previous post I have shown how JsonMergePatch library can be used with console and Asp.Net Core applications.

In this post I will focus on using it with the source generator hand-in-hand of System.Text.Json. Before getting into the details, a new [Patchable] attribute is introduced ease the work:

Patchable

Certain use-cases require to generate wrapper types with the source generation for assemblies that do not directly use Patch<T> (where T is the wrapped source type). This could be a reason for having separate assemblies for entity types, or because of the need of stacking multiple source generators on top of each other.In this case types may be attributed with [Patchable] attribute:

[Patchable]
public class WeatherForecast
{
  //...
}

[Patchable] makes sure to generate wrapper types for source types not used in HTTP requests or method arguments of Patch<T>.

Using it with System.Text.Json source generation

In order to use multiple source generators (System.Text.Json on top of the entities generated by JsonMergePatch), we need to stack them. Today the only way to do it is by enforcing a build order between two projects, while adding the first source generator to the first project built, and the second one to the second project built. To make sure JsonMergePatch source generator works with System.Text.Json's source generator, create two projects:

  1. An entities class library

  2. Add a .NET 6, Asp.Net Core executable (referred as the Application)

Make sure Application references the Entities project. Add entity/POCO/Patchable types to the Entities project. Mark these types with [Patchable] attribute.Add the following nuget packages to the Entities project:

  1. LaDeak.JsonMergePatch.SourceGenerator

  2. LaDeak.JsonMergePatch.Abstractions

This will generate the wrapper types in the Entities project.

In the Application project add a JsonSerializerContext as detailed by System.Text.Json library. In the [JsonSerializable] attributes, list the generated wrapper types from the Entities project using LaDeak.JsonMergePatch.Generated.Safe{TypeName} naming pattern.

[JsonSerializable(typeof(LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.Entities.WeatherForecastWrapped))]
[JsonSerializable(typeof(LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.Entities.CitiesDataWrapped))]
public partial class SampleJsonContext : JsonSerializerContext
{
}

For Asp.Net Core applications, create a new JsonOptions object and extend the serialization options with the derived JsonSerializerContext type. Pass the JsonOptions object to the constructor of JsonMergePatchInputReader.

var mvcBuilder = builder.Services.AddControllers().AddMvcOptions(options =>
{
    LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.TypeRepository.Instance;
    var jsonOptions = new Microsoft.AspNetCore.Http.Json.JsonOptions();
    jsonOptions.SerializerOptions.AddContext<SampleJsonContext>();
    options.InputFormatters.Insert(0, new JsonMergePatchInputReader(jsonOptions));
});

By default JsonMergePatchInputReader uses static value of LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository. In case it needs to be overridden, JsonMergePatchInputReader offers a second constructor parameter, in which any custom TypeRepository may be passed as an argument.

Samples

Sample web applications can be found in the sample folder