SpecWorks.JsonDiff (.NET)
RFC 6902 compliant JSON Patch diff generator for .NET.
Overview
SpecWorks.JsonDiff is a .NET library that generates JSON Patch (RFC 6902) documents by comparing two JSON objects. It provides a simple, standards-focused API that strictly implements RFC 6902 without supporting proprietary formats or extensions.
Installation
dotnet add package SpecWorks.JsonDiff
Or via NuGet Package Manager:
Install-Package SpecWorks.JsonDiff
Quick Start
using SpecWorks.JsonDiff;
using System.Text.Json.Nodes;
// Parse JSON documents
var source = JsonNode.Parse("{\"name\":\"Alice\",\"age\":30}");
var target = JsonNode.Parse("{\"name\":\"Alice\",\"age\":31,\"city\":\"NYC\"}");
// Generate RFC 6902 patch
var generator = new JsonDiffGenerator();
var patch = generator.CreateDiff(source, target);
// Result:
// [
// { "op": "replace", "path": "/age", "value": 31 },
// { "op": "add", "path": "/city", "value": "NYC" }
// ]
// Apply the patch using Microsoft.AspNetCore.JsonPatch
patch.ApplyTo(source);
// source now equals target
Features
- RFC 6902 Compliant - Strictly implements JSON Patch standard
- Simple API - Single method:
CreateDiff(source, target) - Type Safe - Uses
System.Text.Json.Nodes.JsonNodeandMicrosoft.AspNetCore.JsonPatch.JsonPatchDocument - No Configuration - No format options, always RFC 6902
- Well Tested - Comprehensive test suite including RFC examples
- .NET 10.0 and .NET Standard 2.1 - Supports modern and legacy frameworks
Usage Examples
Basic Usage
using SpecWorks.JsonDiff;
using System.Text.Json.Nodes;
var generator = new JsonDiffGenerator();
// Add property
var source1 = JsonNode.Parse("{\"name\":\"Alice\"}");
var target1 = JsonNode.Parse("{\"name\":\"Alice\",\"age\":30}");
var patch1 = generator.CreateDiff(source1, target1);
// Result: [{ "op": "add", "path": "/age", "value": 30 }]
// Remove property
var source2 = JsonNode.Parse("{\"name\":\"Alice\",\"age\":30}");
var target2 = JsonNode.Parse("{\"name\":\"Alice\"}");
var patch2 = generator.CreateDiff(source2, target2);
// Result: [{ "op": "remove", "path": "/age" }]
// Replace property
var source3 = JsonNode.Parse("{\"name\":\"Alice\",\"age\":30}");
var target3 = JsonNode.Parse("{\"name\":\"Alice\",\"age\":31}");
var patch3 = generator.CreateDiff(source3, target3);
// Result: [{ "op": "replace", "path": "/age", "value": 31 }]
Nested Objects
var source = JsonNode.Parse(@"{
""person"": {
""name"": ""Alice"",
""age"": 30
}
}");
var target = JsonNode.Parse(@"{
""person"": {
""name"": ""Alice"",
""age"": 31
}
}");
var patch = generator.CreateDiff(source, target);
// Result: [{ "op": "replace", "path": "/person/age", "value": 31 }]
Complex Updates
var source = JsonNode.Parse(@"{
""name"": ""Alice"",
""age"": 30,
""city"": ""Boston"",
""active"": true
}");
var target = JsonNode.Parse(@"{
""name"": ""Alice"",
""age"": 31,
""country"": ""USA"",
""active"": false
}");
var patch = generator.CreateDiff(source, target);
// Result: Multiple operations (replace age, remove city, add country, replace active)
Dependency Injection
// Startup.cs or Program.cs
services.AddSingleton<IJsonDiffGenerator, JsonDiffGenerator>();
// Usage in a class
public class MyService
{
private readonly IJsonDiffGenerator _diffGenerator;
public MyService(IJsonDiffGenerator diffGenerator)
{
_diffGenerator = diffGenerator;
}
public void CompareDocuments(JsonNode source, JsonNode target)
{
var patch = _diffGenerator.CreateDiff(source, target);
// Use patch...
}
}
RFC 6902 Operations
JSON Patch supports six operation types:
| Operation | Description | Example |
|---|---|---|
add |
Add a value to an object or array | {"op":"add","path":"/name","value":"Alice"} |
remove |
Remove a value | {"op":"remove","path":"/name"} |
replace |
Replace a value | {"op":"replace","path":"/age","value":31} |
move |
Move a value | {"op":"move","from":"/old","path":"/new"} |
copy |
Copy a value | {"op":"copy","from":"/orig","path":"/copy"} |
test |
Test that a value equals specified | {"op":"test","path":"/name","value":"Alice"} |
What This Library Does NOT Support
In accordance with the SpecWorks design philosophy, this library intentionally does NOT support:
- ❌ RFC 7386 (JSON Merge Patch) - Different specification
- ❌ Proprietary diff formats - Vendor-specific formats
- ❌ Non-standard extensions - Custom operations
- ❌ Format configuration options - RFC 6902 only, always
This constraint ensures:
- Interoperability - Works with any RFC 6902 compliant system
- Standards compliance - Predictable, specification-based behavior
- Simplicity - No configuration needed
- Future-proof - Easy migration to native implementation (v2.x)
Implementation Notes
Version 1.x (Current)
The current implementation uses a wrapper architecture with SystemTextJson.JsonDiffPatch as the underlying library. This approach:
- ✅ Provides immediate RFC 6902 functionality
- ✅ Uses battle-tested external library
- ✅ Hides implementation details behind stable interface
- ✅ Enables future migration without breaking changes
Version 2.x (Future)
Future versions will replace the external dependency with a native SpecWorks-generated RFC 6902 implementation. The public API (IJsonDiffGenerator) will remain unchanged, ensuring seamless migration for all consumers.
API Reference
IJsonDiffGenerator Interface
public interface IJsonDiffGenerator
{
JsonPatchDocument CreateDiff(JsonNode source, JsonNode target);
}
CreateDiff Method
Generates an RFC 6902 JSON Patch document representing the differences between source and target.
- Parameters:
source- The source JSON document (JsonNode)target- The target JSON document (JsonNode)
- Returns:
JsonPatchDocumentcontaining RFC 6902 operations - Throws:
ArgumentNullExceptionif source or target is null
JsonDiffGenerator Class
public class JsonDiffGenerator : IJsonDiffGenerator
{
public JsonDiffGenerator();
public JsonPatchDocument CreateDiff(JsonNode source, JsonNode target);
}
Default implementation of IJsonDiffGenerator.
Testing
The library includes comprehensive tests covering:
- ✅ All RFC 6902 operation types
- ✅ RFC examples from specification
- ✅ All JSON data types (string, number, boolean, null, array, object)
- ✅ Edge cases (empty objects, deep nesting)
- ✅ Complex real-world scenarios
- ✅ Null argument validation
Run tests:
cd tests/SpecWorks.JsonDiff.Tests
dotnet test
Building from Source
# Clone the repository
git clone https://github.com/yourusername/spec-works.git
cd spec-works/JsonDiff/dotnet
# Build
dotnet build
# Run tests
dotnet test
# Pack NuGet package
dotnet pack src/SpecWorks.JsonDiff/SpecWorks.JsonDiff.csproj
Project Structure
dotnet/
├── src/
│ └── SpecWorks.JsonDiff/
│ ├── IJsonDiffGenerator.cs # Public interface
│ ├── JsonDiffGenerator.cs # Public implementation
│ ├── Adapters/
│ │ └── SystemTextJsonAdapter.cs # Internal adapter (v1.x)
│ └── SpecWorks.JsonDiff.csproj
├── tests/
│ └── SpecWorks.JsonDiff.Tests/
│ ├── JsonDiffGeneratorTests.cs
│ └── SpecWorks.JsonDiff.Tests.csproj
└── README.md
Dependencies
- System.Text.Json - Included in .NET
- Microsoft.AspNetCore.JsonPatch - For JsonPatchDocument type
- SystemTextJson.JsonDiffPatch - Internal implementation (v1.x only)
Compatibility
- .NET 10.0 - Full support
- .NET Standard 2.1 - Full support for legacy projects
Performance Considerations
The library is designed for correctness and standards compliance. For most use cases, performance is excellent. If you're diffing very large documents (>1MB), consider:
- Streaming comparisons (custom implementation)
- Chunking large documents
- Caching diff results
Contributing
Contributions are welcome! Please:
- Review the Architecture Decision Records
- Follow RFC 6902 strictly
- Add tests for new functionality
- Maintain backward compatibility
License
MIT License - See LICENSE file for details.
References
- RFC 6902 - JavaScript Object Notation (JSON) Patch
- RFC 6901 - JSON Pointer
- JSON Patch Website
- SpecWorks Vision
- Architecture Decision Records
Related Projects
This JSON Patch generator is part of the SpecWorks collection of specification-based software components.
API Reference
- API Documentation - Complete API reference