MergePatchDto 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package MergePatchDto --version 0.1.0
                    
NuGet\Install-Package MergePatchDto -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MergePatchDto" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MergePatchDto" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="MergePatchDto" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MergePatchDto --version 0.1.0
                    
#r "nuget: MergePatchDto, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MergePatchDto@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MergePatchDto&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=MergePatchDto&version=0.1.0
                    
Install as a Cake Tool

MergePatchDto

MergePatchDto is a NuGet package for .NET APIs that want normal DTO-shaped PATCH request bodies, not JSON Patch operation arrays.

It gives PATCH endpoints:

  • a typed allowlist of patchable fields
  • explicit null vs missing tracking
  • optional generated ApplyTo mapping

That lets a PATCH endpoint distinguish:

  • missing property: leave the target unchanged
  • explicit null: assign or clear the target value
  • explicit value: assign the target value

Nested object properties are replacement values, not recursive document merges. Arrays are replacement values, not element-level merges.

Install

dotnet add package MergePatchDto

Quick Start

Define a partial patch DTO and point it at the type it updates:

using MergePatch;

[MergePatch(typeof(Document))]
public partial class UpdateDocumentPatch
{
    public string? Name { get; set; }

    [PatchTo(nameof(Document.Summary))]
    public string? Description { get; set; }

    [PatchIgnore]
    public string? RequestId { get; set; }

    [PatchUsing(nameof(ApplyPriority))]
    public int? Priority { get; set; }

    private static void ApplyPriority(Document target, int? value)
    {
        target.SetPriority(value);
    }
}

Use it directly in an endpoint:

public async Task<IActionResult> Patch(Guid id, UpdateDocumentPatch patch)
{
    var document = await db.Documents.FindAsync(id);
    if (document is null)
    {
        return NotFound();
    }

    patch.ApplyTo(document);

    await db.SaveChangesAsync();
    return NoContent();
}

ApplyTo only applies fields that were present in the JSON body. For example:

{
  "description": null
}

This clears Document.Summary and leaves every omitted property unchanged.

DTO-Shaped PATCH

JsonPatchDocument<T> is useful when clients need RFC 6902 operation documents with paths and operations. MergePatchDto is for endpoints where the public contract is a fixed DTO shape.

The patch DTO is the allowlist. Properties that are not on the patch DTO are not patchable through that endpoint, even if they exist on the target type.

[MergePatch(typeof(Document))]
public partial class UpdateDocumentPatch
{
    public string? Name { get; set; }
    public string? Description { get; set; }
}

The target type lets MergePatchDto generate typed mapping:

patch.ApplyTo(document);

It does not mean every property on Document is patchable. The DTO remains the boundary.

Presence Checks

The hard part of DTO-shaped PATCH is knowing what the client actually sent. After normal deserialization, a missing JSON property and an explicit null can both leave a CLR property as null.

The generated Has API exposes JSON presence, so validation and domain logic do not have to guess from nullable/default values:

var has = patch.Has;

if (has.Name) entity.Name = patch.Name;
if (has.Description) entity.Summary = patch.Description;
if (has.Priority) entity.SetPriority(patch.Priority);

Targetless Patches for Domain Logic

Generated ApplyTo is useful when patch properties map cleanly to a target type. When the update needs domain logic, omit the target type:

using MergePatch;

[MergePatch]
public partial class UpdateDocumentPatch
{
    public string? Name { get; set; }
    public int? PriorityDelta { get; set; }
}

MergePatchDto still generates the JSON converter and Has API:

var has = patch.Has;

if (has.Name)
    entity.Name = patch.Name;

if (has.PriorityDelta)
    entity.IncrementPriority(patch.PriorityDelta.GetValueOrDefault());

Add a target type when you want a typed generated ApplyTo method.

Mapping Rules

By default, patch properties map to target properties with the same CLR name.

  • [PatchTo(nameof(Target.OtherName))] maps a patch property to a differently named target property.
  • [PatchIgnore] excludes client-only values from generated ApplyTo.
  • [PatchUsing(nameof(Method))] calls custom domain logic when the patch property was provided.
  • [MergePatchTarget(typeof(OtherTarget))] adds another typed ApplyTo overload.
  • Targets can be interfaces. In that case, generated ApplyTo accepts the interface and maps only members declared on that interface.

Supported custom apply signatures are:

private static void ApplyPriority(Document target, int? value)
private static void ApplyPriority(UpdateDocumentPatch patch, Document target, int? value)

Target-specific mapping attributes are invalid on targetless patch types and produce an error.

JSON Behavior

MergePatchDto generates a System.Text.Json converter for each patch DTO.

The converter:

  • tracks top-level JSON property presence
  • stores presence by CLR property name
  • respects JsonPropertyNameAttribute
  • respects property-level JsonConverterAttribute
  • respects property-level JsonNumberHandlingAttribute
  • respects JsonIgnoreAttribute read behavior
  • respects JsonSerializerOptions.PropertyNamingPolicy
  • treats explicit null as provided

Unknown JSON properties are ignored by default. To reject them:

[MergePatch(UnknownPropertyHandling = UnknownPropertyHandling.Reject)]
public partial class StrictPatch
{
    public string? Name { get; set; }
}

Diagnostics

Invalid patch shapes and target-specific mappings fail during build with MergePatchDto diagnostics.

Common examples:

  • patch DTO class is not partial
  • target property does not exist
  • target property setter is not accessible
  • patch property type cannot be assigned to the target property type
  • [PatchUsing] method is missing or has an unsupported signature
  • a property combines conflicting mapping attributes

MPD009 is reported when a targetless [MergePatch] DTO uses target-specific mapping attributes such as [PatchTo], [PatchIgnore], or [PatchUsing]. Targetless DTOs do not generate ApplyTo, so those attributes would be misleading. Add a target type with [MergePatch(typeof(Target))] or [MergePatchTarget(typeof(Target))], or remove the target-specific attributes.

Compatibility

MergePatchDto supports SDK-style projects built with the .NET 8 SDK or newer. The runtime assembly targets netstandard2.0, but the source generator runs inside the consumer's C# compiler, so the supported compiler floor is the .NET 8 SDK / Roslyn 4.8.

Limitations

  • MergePatchDto is DTO presence tracking for merge-patch-style endpoints, not a complete RFC 7396 implementation.
  • Patch DTOs must be top-level, non-generic, non-abstract partial classes.
  • Patch DTOs need an accessible parameterless constructor.
  • required members are not supported.
  • Presence tracking is top-level only.
  • Nested object properties are replacement values and are assigned according to the property mapping.
  • Arrays are replacement values, not partial merges.
  • JSON Patch operation arrays, expression-tree mapping, wrapper properties, and DI-driven ApplyTo are not part of this package.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 94 7/5/2026
0.1.0 89 7/5/2026