Skip to content

Pragmatic.Patch

Source-generated PATCH DTOs with tri-state semantics for HTTP PATCH.

A nullable property can’t express the three states a PATCH request needs:

StateMeaningJSON
Field not sentDon’t touch itkey absent
Field explicitly nullClear it"name": null
Field has a valueUpdate it"name": "Alice"

A string? collapses “not sent” and “set to null” into the same null. So hand-rolled PATCH endpoints either overwrite unmentioned fields with null (data loss), skip nulls (can’t clear a field), or demand the full object (defeating PATCH).

// Without Pragmatic.Patch: ambiguous
public class UpdateGuestRequest
{
public string? FirstName { get; set; } // null = "clear it" or "don't touch"?
}

Optional<T> (a readonly struct with explicit tri-state) plus source-generated patch types. The generator emits the apply logic that updates only the properties that were actually sent, and tracks ModifiedProperties for change-aware validation/persistence.

[Patch<Guest>]
public partial class PatchGuestRequest
{
public Optional<string> FirstName { get; init; }
public Optional<string?> Phone { get; init; } // can be sent-as-null to clear
}
// Only sent fields are applied; "not sent" is left untouched
patch.ApplyTo(guest);
Terminal window
dotnet add package Pragmatic.Patch
dotnet add package Pragmatic.SourceGenerator # generates the patch types

Optional<T>, [Patch<T>] generation, JSON deserialization, and ModifiedProperties tracking are functional within the 0.8 preview. See the roadmap.

| Concepts | Tri-state, Optional<T>, generated apply logic, change tracking | | Getting Started | Your first patch DTO and ApplyTo | | Tri-State Semantics | not-sent vs null vs value, JSON mapping, edge cases | | Common Mistakes | The most frequent patch pitfalls | | Troubleshooting | Problem/solution guide with diagnostics |

  • .NET 10.0+
  • Pragmatic.SourceGenerator analyzer

Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Patch is MIT-licensed.