|
1 |
| -# system-text-json-jsondiffpath |
| 1 | +# system-text-json-jsondiffpath |
| 2 | + |
| 3 | +High-performance, low-allocating JSON objects diff and patch extension for `System.Text.Json`. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- Use [jsondiffpatch](https://github.com/benjamine/jsondiffpatch) delta format described [here](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md) |
| 8 | +- Target `.NET Standard 2.0` and leverage latest .NET features |
| 9 | +- Similar diff experience as [jsondiffpatch.net](https://github.com/wbish/jsondiffpatch.net) (based on Newtonsoft.Json) |
| 10 | +- Fast large JSON document diffing with less memory consumption |
| 11 | +- Support smart array diffing (e.g. move detect) using LCS and custom array item matcher |
| 12 | +- Support diffing long text using [google-diff-match-patch](http://code.google.com/p/google-diff-match-patch/), or write your own diff algorithm |
| 13 | +- `JsonNode.Clone` and `JsonNode.DeepEquals` methods |
| 14 | + |
| 15 | +- (_Under development_) Patch, unpatch, formatters etc |
| 16 | + |
| 17 | +# Install |
| 18 | + |
| 19 | +Install from [NuGet.org](https://www.nuget.org/packages/SystemTextJson.JsonDiffPatch/): |
| 20 | + |
| 21 | +``` |
| 22 | +Install-Package SystemTextJson.JsonDiffPatch |
| 23 | +``` |
| 24 | + |
| 25 | +## Usage |
| 26 | +### Diff |
| 27 | + |
| 28 | +```csharp |
| 29 | +// Diff JSON files |
| 30 | +JsonDocument? diff = JsonDiffPatcher.DiffFile(file1, file2); |
| 31 | +// Diff Span<byte> |
| 32 | +JsonDocument? diff = JsonDiffPatcher.Diff(span1, span2); |
| 33 | +// Diff streams |
| 34 | +JsonDocument? diff = JsonDiffPatcher.Diff(stream1, stream2); |
| 35 | +// Diff JSON strings |
| 36 | +JsonDocument? diff = JsonDiffPatcher.Diff(json1, json2); |
| 37 | +// Diff JSON readers |
| 38 | +JsonDocument? diff = JsonDiffPatcher.Diff(ref reader1, ref reader2); |
| 39 | + |
| 40 | +// Diff mutable JsonNode objects |
| 41 | +var node1 = JsonNode.Parse(...); |
| 42 | +var node2 = JsonNode.Parse(...); |
| 43 | +JsonNode? diff = node1.Diff(node2); |
| 44 | +``` |
| 45 | + |
| 46 | +### Clone |
| 47 | + |
| 48 | +```csharp |
| 49 | +var node = JsonNode.Parse(...); |
| 50 | +JsonNode? cloned = node.Clone(); |
| 51 | +``` |
| 52 | + |
| 53 | +### DeepEquals |
| 54 | + |
| 55 | +```csharp |
| 56 | +var node1 = JsonNode.Parse(...); |
| 57 | +var node2 = JsonNode.Parse(...); |
| 58 | +bool equal = node1.DeepEquals(node2); |
| 59 | +``` |
| 60 | + |
| 61 | +### Options |
| 62 | + |
| 63 | +```csharp |
| 64 | +public struct JsonDiffOptions |
| 65 | +{ |
| 66 | + /// <summary> |
| 67 | + /// Specifies whether to suppress detect array move. Default value is <c>false</c>. |
| 68 | + /// </summary> |
| 69 | + public bool SuppressDetectArrayMove { get; set; } |
| 70 | + |
| 71 | + /// <summary> |
| 72 | + /// Specifies whether to include moved item value. Default value is <c>false</c>. |
| 73 | + /// </summary> |
| 74 | + /// <remarks> |
| 75 | + /// See <see link="https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md#array-moves"/>. |
| 76 | + /// </remarks> |
| 77 | + public bool IncludeValueOnMove { get; set; } |
| 78 | + |
| 79 | + /// <summary> |
| 80 | + /// Gets or sets the function to match array items. |
| 81 | + /// </summary> |
| 82 | + public ArrayItemMatch? ArrayItemMatcher { get; set; } |
| 83 | + |
| 84 | + /// <summary> |
| 85 | + /// Gets or sets the function to find key of a <see cref="JsonObject"/> |
| 86 | + /// or <see cref="JsonArray"/>. This is used when matching array items by |
| 87 | + /// their keys. If this function returns <c>null</c>, the items being |
| 88 | + /// compared are treated as "not keyed". When comparing two "not keyed" |
| 89 | + /// objects, their contents are compared. This function is only used when |
| 90 | + /// <see cref="ArrayItemMatcher"/> is set to <c>null</c>. |
| 91 | + /// </summary> |
| 92 | + public Func<JsonNode?, int, object?>? ArrayObjectItemKeyFinder { get; set; } |
| 93 | + |
| 94 | + /// <summary> |
| 95 | + /// Gets or sets whether two instances of JSON object types (object and array) |
| 96 | + /// are considered equal if their position is the same in their parent |
| 97 | + /// arrays regardless of their contents. This property is only used when |
| 98 | + /// <see cref="ArrayItemMatcher"/> is set to <c>null</c>. By settings this |
| 99 | + /// property to <c>true</c>, a diff could be returned faster but larger in |
| 100 | + /// size. Default value is <c>false</c>. |
| 101 | + /// </summary> |
| 102 | + public bool ArrayObjectItemMatchByPosition { get; set; } |
| 103 | + |
| 104 | + /// <summary> |
| 105 | + /// Gets or sets whether to prefer <see cref="ArrayObjectItemKeyFinder"/> and |
| 106 | + /// <see cref="ArrayObjectItemMatchByPosition"/> than using deep value comparison |
| 107 | + /// to match array object items. By settings this property to <c>true</c>, |
| 108 | + /// a diff could be returned faster but larger in size. Default value is <c>false</c>. |
| 109 | + /// </summary> |
| 110 | + public bool PreferFuzzyArrayItemMatch { get; set; } |
| 111 | + |
| 112 | + /// <summary> |
| 113 | + /// Gets or sets the minimum length for diffing texts using <see cref="TextMatcher"/> |
| 114 | + /// or default text diffing algorithm, aka Google's diff-match-patch algorithm. When text |
| 115 | + /// diffing algorithm is not used, text diffing is fallback to value replacement. If this |
| 116 | + /// property is set to <c>0</c>, diffing algorithm is disabled. Default value is <c>0</c>. |
| 117 | + /// </summary> |
| 118 | + public int TextDiffMinLength { get; set; } |
| 119 | + |
| 120 | + /// <summary> |
| 121 | + /// Gets or sets the function to match long texts. |
| 122 | + /// </summary> |
| 123 | + public TextMatch? TextMatcher { get; set; } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +## Benchmark |
| 128 | + |
| 129 | +``` ini |
| 130 | + |
| 131 | +BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19042.631 (20H2/October2020Update) |
| 132 | +Intel Core i7-10750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores |
| 133 | +.NET SDK=5.0.403 |
| 134 | + [Host] : .NET 5.0.12 (5.0.1221.52207), X64 RyuJIT |
| 135 | + DefaultJob : .NET 5.0.12 (5.0.1221.52207), X64 RyuJIT |
| 136 | + |
| 137 | + |
| 138 | +``` |
| 139 | +| Method | Mean | Min | Max | P95 | P80 | Allocated | |
| 140 | +|--------------------------- |------------:|------------:|------------:|------------:|------------:|----------:| |
| 141 | +| DemoObject_JsonNet | 165.4 μs | 158.4 μs | 168.6 μs | 168.0 μs | 167.4 μs | 173 KB | |
| 142 | +| DemoObject_DefaultOptions | 152.8 μs | 146.9 μs | 160.1 μs | 159.3 μs | 155.3 μs | 84 KB | |
| 143 | +| DemoObject_NoArrayMove | 152.0 μs | 147.4 μs | 154.0 μs | 153.6 μs | 153.2 μs | 84 KB | |
| 144 | +| DemoObject_Mutable | 104.1 μs | 101.6 μs | 106.1 μs | 106.0 μs | 105.1 μs | 70 KB | |
| 145 | +| LargeObject_JsonNet | 89,434.7 μs | 84,515.2 μs | 92,858.6 μs | 92,387.7 μs | 91,774.2 μs | 23,628 KB | |
| 146 | +| LargeObject_DefaultOptions | 7,446.1 μs | 7,156.2 μs | 7,794.0 μs | 7,775.8 μs | 7,518.6 μs | 4,085 KB | |
| 147 | +| LargeObject_NoArrayMove | 7,364.3 μs | 7,072.5 μs | 7,575.8 μs | 7,530.3 μs | 7,472.6 μs | 4,087 KB | |
| 148 | +| LargeObject_Mutable | 6,699.4 μs | 6,400.8 μs | 7,017.8 μs | 6,935.1 μs | 6,804.6 μs | 3,538 KB | |
| 149 | + |
| 150 | + |
| 151 | +_\* Generated using example objects [here](https://github.com/weichch/system-text-json-jsondiffpath/tree/main/test/Examples) and benchmark tests [here](https://github.com/weichch/system-text-json-jsondiffpath/tree/main/test/SystemTextJson.JsonDiffPatch.Benchmark/SimpleDiffBenchmark.cs)_ |
| 152 | + |
0 commit comments