Skip to content

Commit 7278d2a

Browse files
authoredMay 13, 2022
New DeepEquals for JsonDocument and JsonElement (#25)
* Add default DeepEquals comparison * Add DeepEquals to JsonElement and JsonDocument * Refactor deep equals * Move node tests to a new directory * Add tests for JsonElement * Add tests for JsonDocument * Paper work for 1.3.0
1 parent f73d599 commit 7278d2a

38 files changed

+2046
-1240
lines changed
 

‎Benchmark.md

+20-18
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1645 (21H1/May2021Update)
1515

1616
## Comparison Modes
1717

18-
| Method | FileSize | Mean | Median | Min | Max | P80 | P95 | Allocated |
19-
|--------- |--------- |---------:|---------:|---------:|---------:|---------:|---------:|----------:|
20-
| RawText | Small | 100.9 μs | 100.3 μs | 98.36 μs | 105.7 μs | 102.0 μs | 104.9 μs | 77 KB |
21-
| Semantic | Small | 102.2 μs | 101.9 μs | 99.37 μs | 107.0 μs | 103.1 μs | 105.6 μs | 76 KB |
18+
| Method | FileSize | Mean | Median | Min | Max | P80 | P95 | Allocated |
19+
|--------- |--------- |----------:|----------:|----------:|----------:|----------:|----------:|----------:|
20+
| RawText | Small | 94.05 μs | 94.09 μs | 93.13 μs | 95.36 μs | 94.35 μs | 94.80 μs | 75 KB |
21+
| Semantic | Small | 104.65 μs | 104.27 μs | 102.67 μs | 107.86 μs | 105.55 μs | 107.76 μs | 75 KB |
2222

2323
\* _All benchmarks are generated using the same small JSON object used in the **System.Text.Json vs Newtonsoft Json** section below, with array move detection enabled (default)._
2424

@@ -28,23 +28,25 @@ BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1645 (21H1/May2021Update)
2828

2929
| Method | FileSize | Mean | Median | Min | Max | P80 | P95 | Allocated |
3030
|------------------- |--------- |------------:|------------:|------------:|------------:|------------:|------------:|----------:|
31-
| **SystemTextJson** | **Small** | **79.09 μs** | **78.25 μs** | **75.72 μs** | **84.89 μs** | **81.91 μs** | **84.16 μs** | **66 KB** |
32-
| JsonNet | Small | 91.98 μs | 92.15 μs | 90.06 μs | 94.50 μs | 92.77 μs | 93.78 μs | 132 KB |
33-
| SystemTextJson_Rfc | Small | 94.88 μs | 94.74 μs | 93.34 μs | 97.80 μs | 95.69 μs | 96.86 μs | 87 KB |
34-
| JsonNet_Rfc | Small | 106.41 μs | 106.01 μs | 103.58 μs | 110.69 μs | 107.38 μs | 109.75 μs | 150 KB |
35-
| **SystemTextJson** | **Large** | **3,717.44 μs** | **3,700.53 μs** | **3,577.00 μs** | **3,913.15 μs** | **3,766.22 μs** | **3,901.23 μs** | **3,258 KB** |
36-
| JsonNet | Large | 4,104.18 μs | 4,085.60 μs | 3,922.80 μs | 4,343.10 μs | 4,199.56 μs | 4,273.98 μs | 4,386 KB |
37-
| SystemTextJson_Rfc | Large | 4,900.93 μs | 4,890.86 μs | 4,772.28 μs | 5,128.16 μs | 4,958.30 μs | 5,021.94 μs | 4,561 KB |
38-
| JsonNet_Rfc | Large | 5,569.83 μs | 5,535.12 μs | 5,354.93 μs | 5,976.46 μs | 5,682.46 μs | 5,822.63 μs | 6,147 KB |
31+
| **SystemTextJson** | **Small** | **76.93 μs** | **76.88 μs** | **75.62 μs** | **79.28 μs** | **77.43 μs** | **78.11 μs** | **67 KB** |
32+
| JsonNet | Small | 84.97 μs | 84.75 μs | 83.72 μs | 87.68 μs | 85.64 μs | 86.38 μs | 132 KB |
33+
| SystemTextJson_Rfc | Small | 91.88 μs | 91.71 μs | 90.70 μs | 95.01 μs | 92.37 μs | 94.37 μs | 89 KB |
34+
| JsonNet_Rfc | Small | 102.15 μs | 102.10 μs | 100.49 μs | 104.37 μs | 102.58 μs | 103.29 μs | 150 KB |
35+
| **SystemTextJson** | **Large** | **3,739.64 μs** | **3,734.25 μs** | **3,626.78 μs** | **3,902.76 μs** | **3,781.22 μs** | **3,844.92 μs** | **3,365 KB** |
36+
| JsonNet | Large | 3,846.70 μs | 3,850.62 μs | 3,760.20 μs | 3,917.07 μs | 3,887.43 μs | 3,896.80 μs | 4,386 KB |
37+
| SystemTextJson_Rfc | Large | 4,897.11 μs | 4,868.30 μs | 4,722.99 μs | 5,196.12 μs | 4,930.06 μs | 5,159.49 μs | 4,667 KB |
38+
| JsonNet_Rfc | Large | 5,260.99 μs | 5,249.26 μs | 5,121.82 μs | 5,487.74 μs | 5,322.84 μs | 5,460.47 μs | 6,147 KB |
3939

4040
### DeepEquals
4141

42-
| Method | FileSize | Mean | Median | Min | Max | P80 | P95 | Allocated |
43-
|--------------- |--------- |------------:|------------:|------------:|------------:|------------:|------------:|----------:|
44-
| **SystemTextJson** | **Small** | **52.92 μs** | **52.89 μs** | **52.00 μs** | **54.46 μs** | **53.31 μs** | **53.90 μs** | **39 KB** |
45-
| JsonNet | Small | 58.82 μs | 58.77 μs | 57.78 μs | 60.41 μs | 59.16 μs | 59.74 μs | 91 KB |
46-
| **SystemTextJson** | **Large** | **2,099.55 μs** | **2,090.78 μs** | **1,963.92 μs** | **2,302.56 μs** | **2,161.80 μs** | **2,223.10 μs** | **1,631 KB** |
47-
| JsonNet | Large | 2,296.54 μs | 2,293.76 μs | 2,239.68 μs | 2,393.52 μs | 2,323.09 μs | 2,378.88 μs | 2,426 KB |
42+
| Method | FileSize | Mean | Median | Min | Max | P80 | P95 | Allocated |
43+
|------------------------ |--------- |------------:|------------:|------------:|------------:|------------:|------------:|----------:|
44+
| **SystemTextJson_Node** | **Small** | **55.10 μs** | **54.96 μs** | **54.14 μs** | **56.93 μs** | **55.49 μs** | **56.57 μs** | **38 KB** |
45+
| SystemTextJson_Document | Small | 40.63 μs | 40.58 μs | 40.08 μs | 41.27 μs | 40.80 μs | 41.12 μs | 26 KB |
46+
| JsonNet | Small | 57.84 μs | 57.62 μs | 57.17 μs | 59.40 μs | 58.07 μs | 58.98 μs | 91 KB |
47+
| **SystemTextJson_Node** | **Large** | **2,143.34 μs** | **2,125.71 μs** | **2,048.46 μs** | **2,328.43 μs** | **2,194.35 μs** | **2,266.60 μs** | **1,571 KB** |
48+
| SystemTextJson_Document | Large | 1,372.31 μs | 1,371.00 μs | 1,352.61 μs | 1,391.00 μs | 1,379.30 μs | 1,388.30 μs | 920 KB |
49+
| JsonNet | Large | 2,208.71 μs | 2,209.77 μs | 2,182.51 μs | 2,246.30 μs | 2,223.80 μs | 2,235.96 μs | 2,426 KB |
4850

4951
### Patch
5052

‎README.md

+61-49
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,40 @@ High-performance, low-allocating JSON object diff and patch extension for System
88

99
- Compatible with [jsondiffpatch delta format](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md)
1010
- Support generating patch document in RFC 6902 JSON Patch format
11-
- Target latest .NET Standard and .NET Framework 4.6.1 (for legacy apps) and leverage latest .NET features
11+
- Target latest **.NET Standard** and **.NET Framework 4.6.1** (for legacy apps) and leverage latest .NET features
1212
- Alternative to [jsondiffpatch.net](https://github.com/wbish/jsondiffpatch.net) which is based on Newtonsoft.Json
1313
- Fast large JSON document diffing with less memory consumption (see [benchmark](https://github.com/weichch/system-text-json-jsondiffpatch/blob/main/Benchmark.md))
1414
- Support smart array diffing (e.g. move detect) using LCS (Longest Common Subsequence) and custom array item matcher
1515
- _(Only when not using RFC 6902 format)_ Support diffing long text using [google-diff-match-patch](http://code.google.com/p/google-diff-match-patch/), or write your own diff algorithm
16-
- Bonus `JsonNode.DeepClone` and `JsonNode.DeepEquals` methods
17-
- Bouns [`JsonValueComparer`](https://github.com/weichch/system-text-json-jsondiffpatch/blob/main/src/SystemTextJson.JsonDiffPatch/JsonValueComparer.cs) that implements semantic comparison of two `JsonValue` objects (including `JsonValue` backed by `JsonElement`)
16+
- Bonus `DeepEquals` method for comparing `JsonDocument`, `JsonElement` and `JsonNode`
17+
- Bonus `DeepClone` method
18+
- Bonus [`JsonValueComparer`](https://github.com/weichch/system-text-json-jsondiffpatch/blob/main/src/SystemTextJson.JsonDiffPatch/JsonValueComparer.cs) that implements semantic comparison of two `JsonValue` objects
1819
- JSON assert for xUnit, MSTest v2 and NUnit with customizable delta output
1920

2021
## Install
2122

2223
#### JsonDiffPatch
2324

2425
```
25-
Install-Package SystemTextJson.JsonDiffPatch
26+
PM> Install-Package SystemTextJson.JsonDiffPatch
2627
```
2728

2829
#### xUnit Assert
2930

3031
```
31-
Install-Package SystemTextJson.JsonDiffPatch.Xunit
32+
PM> Install-Package SystemTextJson.JsonDiffPatch.Xunit
3233
```
3334

3435
#### MSTest v2 Assert
3536

3637
```
37-
Install-Package SystemTextJson.JsonDiffPatch.MSTest
38+
PM> Install-Package SystemTextJson.JsonDiffPatch.MSTest
3839
```
3940

4041
#### NUnit Assert
4142

4243
```
43-
Install-Package SystemTextJson.JsonDiffPatch.NUnit
44+
PM> Install-Package SystemTextJson.JsonDiffPatch.NUnit
4445
```
4546

4647
## Usage
@@ -51,80 +52,91 @@ Install-Package SystemTextJson.JsonDiffPatch.NUnit
5152
// Diff JsonNode
5253
var node1 = JsonNode.Parse("{\"foo\":\"bar\"}");
5354
var node2 = JsonNode.Parse("{\"baz\":\"qux\", \"foo\":\"bar\"}");
54-
JsonNode? diff = node1.Diff(node2);
55+
var diff = node1.Diff(node2);
5556
// Diff with options
56-
JsonNode? diff = node1.Diff(node2, new JsonDiffOptions
57+
var diff = node1.Diff(node2, new JsonDiffOptions
5758
{
5859
JsonElementComparison = JsonElementComparison.Semantic
5960
});
6061
// Diff and convert delta into RFC 6902 JSON Patch format
61-
JsonNode? diff = node1.Diff(node2, new JsonPatchDeltaFormatter());
62+
var diff = node1.Diff(node2, new JsonPatchDeltaFormatter());
6263
// Diff JSON files
63-
JsonNode? diff = JsonDiffPatcher.DiffFile(file1, file2);
64+
var diff = JsonDiffPatcher.DiffFile(file1, file2);
6465
// Diff Span<byte>
65-
JsonNode? diff = JsonDiffPatcher.Diff(span1, span2);
66+
var diff = JsonDiffPatcher.Diff(span1, span2);
6667
// Diff streams
67-
JsonNode? diff = JsonDiffPatcher.Diff(stream1, stream2);
68+
var diff = JsonDiffPatcher.Diff(stream1, stream2);
6869
// Diff JSON strings
69-
JsonNode? diff = JsonDiffPatcher.Diff(json1, json2);
70+
var diff = JsonDiffPatcher.Diff(json1, json2);
7071
// Diff JSON readers
71-
JsonNode? diff = JsonDiffPatcher.Diff(ref reader1, ref reader2);
72+
var diff = JsonDiffPatcher.Diff(ref reader1, ref reader2);
7273
```
7374

74-
### DeepClone
75+
### Patch & Unpatch
7576

7677
```csharp
77-
var node = JsonNode.Parse("{\"foo\":\"bar\"}");
78-
JsonNode? cloned = node.DeepClone();
78+
var node1 = JsonNode.Parse("{\"foo\":\"bar\"}");
79+
var node2 = JsonNode.Parse("{\"baz\":\"qux\", \"foo\":\"bar\"}");
80+
var diff = node1.Diff(node2);
81+
// In-place patch
82+
JsonDiffPatcher.Patch(ref node1, diff);
83+
// Clone & patch
84+
var patched = node1.PatchNew(diff);
85+
// In-place unpatch
86+
JsonDiffPatcher.ReversePatch(ref node1, diff);
87+
// Clone & unpatch
88+
var patched = node1.ReversePatchNew(diff);
7989
```
8090

8191
### DeepEquals
8292

8393
```csharp
84-
var node1 = JsonNode.Parse("{\"foo\":1.0}");
85-
var node2 = JsonNode.Parse("{\"foo\":1}");
86-
// equal is false
87-
bool equal = node1.DeepEquals(node2);
88-
// semanticEqual is true
89-
bool semanticEqual = node1.DeepEquals(node2, JsonElementComparison.Semantic);
90-
```
91-
92-
### Semantic Value Comparison
93-
```csharp
94-
var node1 = JsonNode.Parse("\"2019-11-27\"");
95-
var node2 = JsonNode.Parse("\"2019-11-27T00:00:00.000\"");
96-
// dateCompare is 0
97-
var dateCompare = JsonValueComparer.Compare(node1, node2);
94+
// JsonDocument
95+
var doc1 = JsonDocument.Parse("{\"foo\":1}");
96+
var doc2 = JsonDocument.Parse("{\"foo\":1.0}");
97+
var equal = doc1.DeepEquals(doc2);
98+
var textEqual = doc1.DeepEquals(doc2, JsonElementComparison.RawText);
99+
var semanticEqual = doc1.DeepEquals(doc2, JsonElementComparison.Semantic);
98100

99-
var node3 = JsonNode.Parse("1");
100-
var node4 = JsonNode.Parse("1.00");
101-
// numCompare is 0
102-
var numCompare = JsonValueComparer.Compare(node3, node4);
101+
// JsonNode
102+
var node1 = JsonNode.Parse("{\"foo\":1}");
103+
var node2 = JsonNode.Parse("{\"foo\":1.0}");
104+
var equal = node1.DeepEquals(node2);
105+
var textEqual = node1.DeepEquals(node2, JsonElementComparison.RawText);
106+
var semanticEqual = node1.DeepEquals(node2, JsonElementComparison.Semantic);
103107
```
104108

105-
### Patch & Unpatch
109+
### DeepClone
106110

107111
```csharp
108-
var node1 = JsonNode.Parse("{\"foo\":\"bar\"}");
109-
var node2 = JsonNode.Parse("{\"baz\":\"qux\", \"foo\":\"bar\"}");
110-
JsonNode? diff = node1.Diff(node2);
111-
// In-place patch
112-
JsonDiffPatcher.Patch(ref node1, diff);
113-
// Clone & patch
114-
node1.PatchNew(diff);
115-
// In-place unpatch
116-
JsonDiffPatcher.ReversePatch(ref node1, diff);
117-
// Clone & unpatch
118-
node1.ReversePatchNew(diff);
112+
var node = JsonNode.Parse("{\"foo\":\"bar\"}");
113+
var cloned = node.DeepClone();
119114
```
120115

121116
### Default Options
122117

123118
```csharp
119+
// Default diff options
124120
JsonDiffPatcher.DefaultOptions = () => new JsonDiffOptions
125121
{
126122
JsonElementComparison = JsonElementComparison.Semantic
127123
};
124+
125+
// Default comparison mode for DeepEquals
126+
JsonDiffPatcher.DefaultComparison = JsonElementComparison.Semantic;
127+
```
128+
129+
### Semantic Value Comparison
130+
```csharp
131+
var node1 = JsonNode.Parse("\"2019-11-27\"");
132+
var node2 = JsonNode.Parse("\"2019-11-27T00:00:00.000\"");
133+
// dateCompare is 0
134+
var dateCompare = JsonValueComparer.Compare(node1, node2);
135+
136+
var node3 = JsonNode.Parse("1");
137+
var node4 = JsonNode.Parse("1.00");
138+
// numCompare is 0
139+
var numCompare = JsonValueComparer.Compare(node3, node4);
128140
```
129141

130142
### Assert (Unit Testing)
@@ -178,4 +190,4 @@ Delta:
178190

179191
## Benchmark
180192

181-
[Benchmark results](https://github.com/weichch/system-text-json-jsondiffpatch/blob/main/Benchmark.md) were generated using example objects [here](https://github.com/weichch/system-text-json-jsondiffpatch/tree/main/test/Examples) and benchmark tests [here](https://github.com/weichch/system-text-json-jsondiffpatch/tree/main/test/SystemTextJson.JsonDiffPatch.Benchmark/).
193+
See detailed [benchmark results](https://github.com/weichch/system-text-json-jsondiffpatch/blob/main/Benchmark.md).

‎ReleaseNotes.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Release Notes
22

3+
## 1.3.0
4+
5+
- **Added `DeepEquals` implementation for `JsonDocument` and `JsonElement`**
6+
- Performance improvements in raw text comparison mode
7+
- Removed unnecessary allocation when default diff option is used
8+
- Removed one `DeepEquals` overload that was accidentally exposed as a public method
9+
310
## 1.2.0
411

512
- Major performance improvement in array comparison

‎src/SystemTextJson.JsonDiffPatch/Diffs/JsonDiffOptions.cs

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Runtime.CompilerServices;
23
using System.Text.Json.JsonDiffPatch.Diffs;
34
using System.Text.Json.Nodes;
45

@@ -9,8 +10,7 @@ namespace System.Text.Json.JsonDiffPatch
910
/// </summary>
1011
public class JsonDiffOptions
1112
{
12-
internal static readonly JsonDiffOptions Default = new();
13-
private JsonComparerOptions _comparerOptions;
13+
private JsonElementComparison? _jsonElementComparison;
1414

1515
/// <summary>
1616
/// Specifies whether to suppress detect array move. Default value is <c>false</c>.
@@ -49,22 +49,18 @@ public class JsonDiffOptions
4949
/// <summary>
5050
/// Gets or sets the mode to compare two <see cref="JsonElement"/> instances.
5151
/// </summary>
52-
public JsonElementComparison JsonElementComparison { get; set; }
52+
public JsonElementComparison JsonElementComparison
53+
{
54+
get => _jsonElementComparison ?? JsonDiffPatcher.DefaultComparison;
55+
set => _jsonElementComparison = value;
56+
}
5357

5458
/// <summary>
5559
/// Gets or sets the <see cref="JsonValue"/> comparer.
5660
/// </summary>
5761
public IEqualityComparer<JsonValue>? ValueComparer { get; set; }
5862

59-
internal ref JsonComparerOptions CreateComparerOptions()
60-
{
61-
if (JsonElementComparison != _comparerOptions.JsonElementComparison
62-
|| !ReferenceEquals(ValueComparer, _comparerOptions.ValueComparer))
63-
{
64-
_comparerOptions = new JsonComparerOptions(JsonElementComparison, ValueComparer);
65-
}
66-
67-
return ref _comparerOptions;
68-
}
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
internal JsonComparerOptions CreateComparerOptions() => new(JsonElementComparison, ValueComparer);
6965
}
7066
}

‎src/SystemTextJson.JsonDiffPatch/Diffs/JsonDiffPatcher.Array.cs

+24-23
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ static partial class JsonDiffPatcher
1111
// https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md#array-with-inner-changes
1212
private static void DiffArray(
1313
ref JsonDiffDelta delta,
14-
JsonArray left,
14+
JsonArray left,
1515
JsonArray right,
16-
JsonDiffOptions options)
16+
JsonDiffOptions? options)
1717
{
1818
// Both are empty arrays
1919
if (left.Count == 0 && right.Count == 0)
2020
{
2121
return;
2222
}
2323

24-
ref var comparerOptions = ref options.CreateComparerOptions();
24+
var comparerOptions = options?.CreateComparerOptions() ?? default;
2525

2626
// Find command head
2727
int commonHead;
@@ -41,8 +41,8 @@ private static void DiffArray(
4141
// Find common tail
4242
int commonTail;
4343
for (commonTail = 0;
44-
commonHead + commonTail < left.Count && commonHead + commonTail < right.Count;
45-
commonTail++)
44+
commonHead + commonTail < left.Count && commonHead + commonTail < right.Count;
45+
commonTail++)
4646
{
4747
var leftIndex = left.Count - 1 - commonTail;
4848
var rightIndex = right.Count - 1 - commonTail;
@@ -67,21 +67,21 @@ private static void DiffArray(
6767

6868
// Otherwise, the left is shorter, so there were items added to the middle of the right array
6969
for (var i = commonHead /* pointed to the first non-equal item */;
70-
i + commonTail < right.Count;
71-
i++)
70+
i + commonTail < right.Count;
71+
i++)
7272
{
7373
delta.ArrayChange(i, false, JsonDiffDelta.CreateAdded(right[i]));
7474
}
7575

7676
return;
7777
}
78-
78+
7979
if (commonHead + commonTail == right.Count)
8080
{
8181
// The right is shorter, so there were items removed from the middle of left array
8282
for (var i = commonHead /* pointed to the first non-equal item */;
83-
i + commonTail < left.Count;
84-
i++)
83+
i + commonTail < left.Count;
84+
i++)
8585
{
8686
delta.ArrayChange(i, true, JsonDiffDelta.CreateDeleted(left[i]));
8787
}
@@ -131,15 +131,15 @@ private static void DiffArray(
131131
{
132132
// Added, detect if it's moved item
133133
var isMoved = false;
134-
if (!options.SuppressDetectArrayMove && removedIndices is not null)
134+
if (options?.SuppressDetectArrayMove != true && removedIndices is not null)
135135
{
136136
for (var j = 0; j < removedIndices.Count; j++)
137137
{
138138
var removedLeftIndex = removedIndices[j];
139139
if (lcs.AreEqual(
140-
removedLeftIndex - commonHead /* Deleted in left */,
141-
i - commonHead /* Current in right */,
142-
out var isDeepEqual))
140+
removedLeftIndex - commonHead /* Deleted in left */,
141+
i - commonHead /* Current in right */,
142+
out var isDeepEqual))
143143
{
144144
delta.ArrayMoveFromDeleted(removedLeftIndex, i);
145145

@@ -178,7 +178,7 @@ private static void DiffArray(
178178
static void AddDiffResult(
179179
ref JsonDiffDelta delta,
180180
ref ArrayItemMatchContext context,
181-
JsonDiffOptions options)
181+
JsonDiffOptions? options)
182182
{
183183
if (context.IsDeepEqual)
184184
{
@@ -194,7 +194,7 @@ static void AddDiffResult(
194194
}
195195
}
196196

197-
internal static bool MatchArrayItem(ref ArrayItemMatchContext context, JsonDiffOptions options,
197+
internal static bool MatchArrayItem(ref ArrayItemMatchContext context, JsonDiffOptions? options,
198198
in JsonComparerOptions comparerOptions)
199199
{
200200
if (context.Left.DeepEquals(context.Right, comparerOptions))
@@ -203,15 +203,16 @@ internal static bool MatchArrayItem(ref ArrayItemMatchContext context, JsonDiffO
203203
return true;
204204
}
205205

206-
if (context.Left is JsonObject or JsonArray && context.Right is JsonObject or JsonArray)
206+
if (options is not null && context.Left is JsonObject or JsonArray &&
207+
context.Right is JsonObject or JsonArray)
207208
{
208209
if (FuzzyMatchArrayItem(ref context, options, out var fuzzyResult))
209210
{
210211
return fuzzyResult;
211212
}
212213
}
213214

214-
if (options.ArrayItemMatcher is not null)
215+
if (options?.ArrayItemMatcher is not null)
215216
{
216217
return options.ArrayItemMatcher(ref context);
217218
}
@@ -221,18 +222,18 @@ internal static bool MatchArrayItem(ref ArrayItemMatchContext context, JsonDiffO
221222

222223
internal static bool MatchArrayItem(
223224
ref ArrayItemMatchContext context,
224-
ref JsonValueComparisonContext valueContextLeft,
225-
ref JsonValueComparisonContext valueContextRight,
226-
JsonDiffOptions options,
225+
ref JsonValueWrapper wrapperLeft,
226+
ref JsonValueWrapper wrapperRight,
227+
JsonDiffOptions? options,
227228
in JsonComparerOptions comparerOptions)
228229
{
229-
if (valueContextLeft.DeepEquals(ref valueContextRight, comparerOptions))
230+
if (wrapperLeft.DeepEquals(ref wrapperRight, comparerOptions))
230231
{
231232
context.DeepEqual();
232233
return true;
233234
}
234235

235-
if (options.ArrayItemMatcher is not null)
236+
if (options?.ArrayItemMatcher is not null)
236237
{
237238
return options.ArrayItemMatcher(ref context);
238239
}

‎src/SystemTextJson.JsonDiffPatch/Diffs/JsonDiffPatcher.Diff.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ static partial class JsonDiffPatcher
200200
JsonDiffOptions? options)
201201
{
202202
var delta = new JsonDiffDelta();
203+
options ??= DefaultOptions?.Invoke();
203204
DiffInternal(ref delta, left, right, options);
204205

205206
if (formatter is not null)
@@ -223,8 +224,6 @@ private static void DiffInternal(
223224
{
224225
Debug.Assert(delta.Document is null);
225226

226-
options ??= DefaultOptions?.Invoke() ?? JsonDiffOptions.Default;
227-
228227
left ??= "";
229228
right ??= "";
230229

@@ -246,13 +245,14 @@ private static void DiffInternal(
246245
// Compare two long texts
247246
if (IsLongText(left, right, options, out var leftText, out var rightText))
248247
{
249-
DiffLongText(ref delta, leftText!, rightText!, options);
248+
Debug.Assert(options is not null);
249+
DiffLongText(ref delta, leftText!, rightText!, options!);
250250
return;
251251
}
252252

253253
// None of the above methods returned a result, fallback to check if both values are deeply equal
254254
// This should also handle DateTime and other CLR types that are strings in JSON
255-
ref var comparerOptions = ref options.CreateComparerOptions();
255+
var comparerOptions = options?.CreateComparerOptions() ?? default;
256256
if (!left.DeepEquals(right, comparerOptions))
257257
{
258258
delta.Modified(left, right);

‎src/SystemTextJson.JsonDiffPatch/Diffs/JsonDiffPatcher.Object.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ private static void DiffObject(
1212
ref JsonDiffDelta delta,
1313
JsonObject left,
1414
JsonObject right,
15-
JsonDiffOptions options)
15+
JsonDiffOptions? options)
1616
{
1717
var leftProperties = (left as IDictionary<string, JsonNode?>).Keys;
1818
var rightProperties = (right as IDictionary<string, JsonNode?>).Keys;

‎src/SystemTextJson.JsonDiffPatch/Diffs/JsonDiffPatcher.Text.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,18 @@ private static void DiffLongText(
4545
private static bool IsLongText(
4646
JsonNode? left,
4747
JsonNode? right,
48-
JsonDiffOptions options,
48+
JsonDiffOptions? options,
4949
out string? leftText,
5050
out string? rightText)
5151
{
5252
leftText = null;
5353
rightText = null;
5454

55+
if (options is null)
56+
{
57+
return false;
58+
}
59+
5560
if (left is not JsonValue || right is not JsonValue)
5661
{
5762
return false;

‎src/SystemTextJson.JsonDiffPatch/Diffs/Lcs.cs

+20-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Buffers;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Runtime.CompilerServices;
45
using System.Text.Json.Nodes;
56

67
namespace System.Text.Json.JsonDiffPatch.Diffs
@@ -14,20 +15,22 @@ internal readonly ref struct Lcs
1415
private readonly Dictionary<int, LcsEntry>? _lookupByRightIndex;
1516
private readonly int[]? _matrixRented;
1617
private readonly int[]? _matchMatrixRented;
17-
private readonly JsonValueComparisonContext[]? _comparisonContextCacheRented;
18+
private readonly JsonValueWrapper[]? _wrapperCacheRented;
1819
private readonly int _rowSize;
1920

21+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2022
private Lcs(List<LcsEntry> indices, int[] matrixRented, int[] matchMatrixRented,
21-
JsonValueComparisonContext[]? comparisonContextCacheRented, int rowSize)
23+
JsonValueWrapper[]? wrapperCacheRented, int rowSize)
2224
{
2325
_lookupByLeftIndex = indices.ToDictionary(entry => entry.LeftIndex);
2426
_lookupByRightIndex = indices.ToDictionary(entry => entry.RightIndex);
2527
_matrixRented = matrixRented;
2628
_matchMatrixRented = matchMatrixRented;
27-
_comparisonContextCacheRented = comparisonContextCacheRented;
29+
_wrapperCacheRented = wrapperCacheRented;
2830
_rowSize = rowSize;
2931
}
3032

33+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3134
public bool FindLeftIndex(int index, out LcsEntry result)
3235
{
3336
if (_lookupByLeftIndex is null)
@@ -39,6 +42,7 @@ public bool FindLeftIndex(int index, out LcsEntry result)
3942
return _lookupByLeftIndex.TryGetValue(index, out result);
4043
}
4144

45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4246
public bool FindRightIndex(int index, out LcsEntry result)
4347
{
4448
if (_lookupByRightIndex is null)
@@ -50,6 +54,7 @@ public bool FindRightIndex(int index, out LcsEntry result)
5054
return _lookupByRightIndex.TryGetValue(index, out result);
5155
}
5256

57+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5358
public bool AreEqual(int indexX, int indexY, out bool deepEqual)
5459
{
5560
if (_matchMatrixRented is null)
@@ -75,13 +80,13 @@ public void Free()
7580
ArrayPool<int>.Shared.Return(_matchMatrixRented);
7681
}
7782

78-
if (_comparisonContextCacheRented is not null)
83+
if (_wrapperCacheRented is not null)
7984
{
80-
ArrayPool<JsonValueComparisonContext>.Shared.Return(_comparisonContextCacheRented);
85+
ArrayPool<JsonValueWrapper>.Shared.Return(_wrapperCacheRented);
8186
}
8287
}
8388

84-
public static Lcs Get(Span<JsonNode?> x, Span<JsonNode?> y, JsonDiffOptions options)
89+
public static Lcs Get(Span<JsonNode?> x, Span<JsonNode?> y, JsonDiffOptions? options)
8590
{
8691
if (x.Length == 0 || y.Length == 0)
8792
{
@@ -98,27 +103,27 @@ public static Lcs Get(Span<JsonNode?> x, Span<JsonNode?> y, JsonDiffOptions opti
98103
var matchMatrixSpan = matchMatrixRented.AsSpan(0, matrixLength);
99104
// For performance reasons, we set materialized values into a cache.
100105
// We only cache JSON values as they are more efficient to cache than objects and arrays.
101-
var valueCacheRented = ArrayPool<JsonValueComparisonContext>.Shared.Rent(x.Length + y.Length);
102-
var valueCacheSpan = valueCacheRented.AsSpan(0, x.Length + y.Length);
103-
ref var comparerOptions = ref options.CreateComparerOptions();
106+
var wrapperCacheRented = ArrayPool<JsonValueWrapper>.Shared.Rent(x.Length + y.Length);
107+
var wrapperCacheSpan = wrapperCacheRented.AsSpan(0, x.Length + y.Length);
108+
var comparerOptions = options?.CreateComparerOptions() ?? default;
104109

105110
matrix.Fill(0);
106111
matchMatrixSpan.Fill(0);
107-
valueCacheSpan.Fill(default);
112+
wrapperCacheSpan.Fill(default);
108113

109114
for (var i = 1; i < m; i++)
110115
{
111116
if (x[i - 1] is JsonValue jsonValueX)
112117
{
113-
valueCacheSpan[i - 1] = new JsonValueComparisonContext(jsonValueX, true);
118+
wrapperCacheSpan[i - 1] = new JsonValueWrapper(jsonValueX);
114119
}
115120
}
116121

117122
for (var j = 1; j < n; j++)
118123
{
119124
if (y[j - 1] is JsonValue jsonValueY)
120125
{
121-
valueCacheSpan[x.Length + j - 1] = new JsonValueComparisonContext(jsonValueY, true);
126+
wrapperCacheSpan[x.Length + j - 1] = new JsonValueWrapper(jsonValueY);
122127
}
123128
}
124129

@@ -149,8 +154,8 @@ public static Lcs Get(Span<JsonNode?> x, Span<JsonNode?> y, JsonDiffOptions opti
149154
if (x[i - 1] is JsonValue && y[j - 1] is JsonValue)
150155
{
151156
itemMatched = JsonDiffPatcher.MatchArrayItem(ref matchContext,
152-
ref valueCacheSpan[i - 1],
153-
ref valueCacheSpan[x.Length + j - 1],
157+
ref wrapperCacheSpan[i - 1],
158+
ref wrapperCacheSpan[x.Length + j - 1],
154159
options,
155160
comparerOptions);
156161
}
@@ -222,7 +227,7 @@ public static Lcs Get(Span<JsonNode?> x, Span<JsonNode?> y, JsonDiffOptions opti
222227
}
223228
}
224229

225-
return new Lcs(entries, matrixRented, matchMatrixRented, valueCacheRented, n);
230+
return new Lcs(entries, matrixRented, matchMatrixRented, wrapperCacheRented, n);
226231
}
227232

228233
internal readonly struct LcsEntry

‎src/SystemTextJson.JsonDiffPatch/JsonComparerOptions.cs

+9-18
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,20 @@
33

44
namespace System.Text.Json.JsonDiffPatch
55
{
6-
/// <summary>
7-
/// Represents the options for internal JSON comparer.
8-
/// </summary>
9-
public readonly struct JsonComparerOptions
10-
{
11-
/// <summary>
12-
/// Creates an instance of the options.
13-
/// </summary>
14-
public JsonComparerOptions(JsonElementComparison jsonElementComparison,
6+
internal readonly struct JsonComparerOptions
7+
{
8+
private readonly JsonElementComparison? _jsonElementComparison;
9+
10+
public JsonComparerOptions(JsonElementComparison? jsonElementComparison,
1511
IEqualityComparer<JsonValue>? valueComparer)
1612
{
17-
JsonElementComparison = jsonElementComparison;
13+
_jsonElementComparison = jsonElementComparison;
1814
ValueComparer = valueComparer;
1915
}
2016

21-
/// <summary>
22-
/// Gets the mode to compare two <see cref="JsonElement"/> instances.
23-
/// </summary>
24-
public JsonElementComparison JsonElementComparison { get; }
17+
public JsonElementComparison JsonElementComparison
18+
=> _jsonElementComparison ?? JsonDiffPatcher.DefaultComparison;
2519

26-
/// <summary>
27-
/// Gets the value comparer.
28-
/// </summary>
2920
public IEqualityComparer<JsonValue>? ValueComparer { get; }
3021
}
31-
}
22+
}

‎src/SystemTextJson.JsonDiffPatch/JsonDiffPatcher.DeepEquals.cs

+169-19
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static bool DeepEquals(this JsonNode? left, JsonNode? right, JsonElementC
3636
public static bool DeepEquals(this JsonNode? left, JsonNode? right, IEqualityComparer<JsonValue> valueComparer)
3737
{
3838
_ = valueComparer ?? throw new ArgumentNullException(nameof(valueComparer));
39-
return DeepEquals(left, right, new JsonComparerOptions(default, valueComparer));
39+
return DeepEquals(left, right, new JsonComparerOptions(null, valueComparer));
4040
}
4141

4242
/// <summary>
@@ -45,7 +45,7 @@ public static bool DeepEquals(this JsonNode? left, JsonNode? right, IEqualityCom
4545
/// <param name="left">The left value.</param>
4646
/// <param name="right">The right value.</param>
4747
/// <param name="comparerOptions">The value comparer options.</param>
48-
public static bool DeepEquals(this JsonNode? left, JsonNode? right, in JsonComparerOptions comparerOptions)
48+
internal static bool DeepEquals(this JsonNode? left, JsonNode? right, in JsonComparerOptions comparerOptions)
4949
{
5050
Debug.Assert(left is null or JsonObject or JsonArray or JsonValue);
5151
Debug.Assert(right is null or JsonObject or JsonArray or JsonValue);
@@ -69,26 +69,100 @@ public static bool DeepEquals(this JsonNode? left, JsonNode? right, in JsonCompa
6969
};
7070
}
7171

72-
private static bool ObjectEquals(JsonObject obj1, JsonObject obj2, in JsonComparerOptions comparerOptions)
72+
/// <summary>
73+
/// Determines whether two <see cref="JsonElement"/> objects are deeply equal.
74+
/// </summary>
75+
/// <param name="left">The left value.</param>
76+
/// <param name="right">The right value.</param>
77+
/// <param name="elementComparison">The JSON element comparison.</param>
78+
public static bool DeepEquals(this JsonDocument? left, JsonDocument? right,
79+
JsonElementComparison? elementComparison = null)
80+
{
81+
if (ReferenceEquals(left, right))
82+
{
83+
return true;
84+
}
85+
86+
if (left is null || right is null)
87+
{
88+
return false;
89+
}
90+
91+
return left.RootElement.DeepEquals(right.RootElement, elementComparison);
92+
}
93+
94+
/// <summary>
95+
/// Determines whether two <see cref="JsonElement"/> objects are deeply equal.
96+
/// </summary>
97+
/// <param name="left">The left value.</param>
98+
/// <param name="right">The right value.</param>
99+
/// <param name="elementComparison">The JSON element comparison.</param>
100+
public static bool DeepEquals(this in JsonElement left, in JsonElement right,
101+
JsonElementComparison? elementComparison = null)
73102
{
74-
if (obj1.Count == 0 && obj2.Count == 0)
103+
if (left.ValueKind != right.ValueKind)
104+
{
105+
return false;
106+
}
107+
108+
elementComparison ??= DefaultComparison;
109+
110+
switch (left.ValueKind)
111+
{
112+
case JsonValueKind.Number:
113+
var leftNumber = new JsonNumber(left);
114+
var rightNumber = new JsonNumber(right);
115+
116+
return elementComparison is JsonElementComparison.RawText
117+
? leftNumber.RawTextEquals(ref rightNumber)
118+
: leftNumber.CompareTo(ref rightNumber) == 0;
119+
120+
case JsonValueKind.String:
121+
var leftString = new JsonString(left);
122+
var rightString = new JsonString(right);
123+
124+
return elementComparison is JsonElementComparison.RawText
125+
? leftString.ValueEquals(ref rightString)
126+
: leftString.Equals(ref rightString);
127+
128+
case JsonValueKind.Object:
129+
return ObjectEquals(left, right, elementComparison.Value);
130+
131+
case JsonValueKind.Array:
132+
return ArrayEquals(left, right, elementComparison.Value);
133+
134+
case JsonValueKind.True:
135+
case JsonValueKind.False:
136+
case JsonValueKind.Null:
137+
case JsonValueKind.Undefined:
138+
return true;
139+
140+
default:
141+
throw new ArgumentOutOfRangeException(nameof(left.ValueKind),
142+
$"Unexpected JSON value kind {left.ValueKind:G}.");
143+
}
144+
}
145+
146+
private static bool ObjectEquals(JsonObject x, JsonObject y, in JsonComparerOptions comparerOptions)
147+
{
148+
if (x.Count == 0 && y.Count == 0)
75149
{
76150
// Empty objects
77151
return true;
78152
}
79153

80-
if (obj1.Count != obj2.Count)
154+
if (x.Count != y.Count)
81155
{
82156
// Property count mismatch
83157
return false;
84158
}
85159

86-
foreach (var kvp in obj1)
160+
foreach (var kvp in x)
87161
{
88162
var propertyName = kvp.Key;
89163
var obj1Value = kvp.Value;
90164

91-
if (!obj2.TryGetPropertyValue(propertyName, out var obj2Value))
165+
if (!y.TryGetPropertyValue(propertyName, out var obj2Value))
92166
{
93167
// Missing property
94168
return false;
@@ -104,22 +178,73 @@ private static bool ObjectEquals(JsonObject obj1, JsonObject obj2, in JsonCompar
104178
return true;
105179
}
106180

107-
private static bool ArrayEquals(JsonArray arr1, JsonArray arr2, in JsonComparerOptions comparerOptions)
181+
private static bool ObjectEquals(in JsonElement x, in JsonElement y, JsonElementComparison elementComparison)
182+
{
183+
EnumerateProperty(x, out var propertiesX);
184+
EnumerateProperty(y, out var propertiesY);
185+
186+
if (propertiesX is null && propertiesY is null)
187+
{
188+
return true;
189+
}
190+
191+
if (propertiesX is null || propertiesY is null)
192+
{
193+
return false;
194+
}
195+
196+
if (propertiesX.Count != propertiesY.Count)
197+
{
198+
return false;
199+
}
200+
201+
foreach (var kvp in propertiesX)
202+
{
203+
if (!propertiesY.TryGetValue(kvp.Key, out var propertyY))
204+
{
205+
return false;
206+
}
207+
208+
if (!kvp.Value.Value.DeepEquals(propertyY.Value, elementComparison))
209+
{
210+
return false;
211+
}
212+
}
213+
214+
return true;
215+
216+
static void EnumerateProperty(in JsonElement element, out Dictionary<string, JsonProperty>? properties)
217+
{
218+
properties = null;
219+
220+
foreach (var property in element.EnumerateObject())
221+
{
222+
if (properties is null)
223+
{
224+
properties = new Dictionary<string, JsonProperty>();
225+
}
226+
227+
properties[property.Name] = property;
228+
}
229+
}
230+
}
231+
232+
private static bool ArrayEquals(JsonArray x, JsonArray y, in JsonComparerOptions comparerOptions)
108233
{
109-
if (arr1.Count == 0 && arr2.Count == 0)
234+
if (x.Count == 0 && y.Count == 0)
110235
{
111236
return true;
112237
}
113238

114-
if (arr1.Count != arr2.Count)
239+
if (x.Count != y.Count)
115240
{
116241
// Item count mismatch
117242
return false;
118243
}
119244

120-
for (var i = 0; i < arr1.Count; i++)
245+
for (var i = 0; i < x.Count; i++)
121246
{
122-
if (!DeepEquals(arr1[i], arr2[i], comparerOptions))
247+
if (!DeepEquals(x[i], y[i], comparerOptions))
123248
{
124249
return false;
125250
}
@@ -128,25 +253,50 @@ private static bool ArrayEquals(JsonArray arr1, JsonArray arr2, in JsonComparerO
128253
return true;
129254
}
130255

131-
private static bool ValueEquals(JsonValue val1, JsonValue val2, in JsonComparerOptions comparerOptions)
256+
private static bool ArrayEquals(in JsonElement x, in JsonElement y, JsonElementComparison elementComparison)
257+
{
258+
if (x.GetArrayLength() != y.GetArrayLength())
259+
{
260+
return false;
261+
}
262+
263+
using var enumeratorX = x.EnumerateArray();
264+
using var enumeratorY = y.EnumerateArray();
265+
266+
while (enumeratorX.MoveNext())
267+
{
268+
if (!enumeratorY.MoveNext() ||
269+
!enumeratorX.Current.DeepEquals(enumeratorY.Current, elementComparison))
270+
{
271+
return false;
272+
}
273+
}
274+
275+
Debug.Assert(enumeratorX.MoveNext() == false);
276+
Debug.Assert(enumeratorY.MoveNext() == false);
277+
278+
return true;
279+
}
280+
281+
private static bool ValueEquals(JsonValue x, JsonValue y, in JsonComparerOptions comparerOptions)
132282
{
133283
var valueComparer = comparerOptions.ValueComparer;
134284
if (valueComparer is not null)
135285
{
136-
var hash1 = valueComparer.GetHashCode(val1);
137-
var hash2 = valueComparer.GetHashCode(val2);
286+
var hash1 = valueComparer.GetHashCode(x);
287+
var hash2 = valueComparer.GetHashCode(y);
138288

139289
if (hash1 != hash2)
140290
{
141291
return false;
142292
}
143293

144-
return valueComparer.Equals(val1, val2);
294+
return valueComparer.Equals(x, y);
145295
}
146296

147-
var ctx1 = new JsonValueComparisonContext(val1);
148-
var ctx2 = new JsonValueComparisonContext(val2);
149-
return ctx1.DeepEquals(ref ctx2, comparerOptions.JsonElementComparison);
297+
var wrapperX = new JsonValueWrapper(x);
298+
var wrapperY = new JsonValueWrapper(y);
299+
return wrapperX.DeepEquals(ref wrapperY, comparerOptions.JsonElementComparison);
150300
}
151301
}
152302
}

‎src/SystemTextJson.JsonDiffPatch/JsonDiffPatcher.cs

+5
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@ public static partial class JsonDiffPatcher
99
/// Gets or sets the default diff options.
1010
/// </summary>
1111
public static Func<JsonDiffOptions>? DefaultOptions { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets the default comparison mode used by <c>DeepEquals</c> to compare <see cref="JsonElement"/>.
15+
/// </summary>
16+
public static JsonElementComparison DefaultComparison { get; set; } = JsonElementComparison.RawText;
1217
}
1318
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
3+
using System.Text.Json.Nodes;
4+
5+
namespace System.Text.Json.JsonDiffPatch
6+
{
7+
internal struct JsonNumber
8+
{
9+
private long? _longValue;
10+
private decimal? _decimalValue;
11+
private double? _doubleValue;
12+
private readonly float? _floatValue;
13+
private bool _isValueRead;
14+
15+
// Keep as field to avoid copy
16+
public readonly JsonElement Element;
17+
18+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
19+
public JsonNumber(in JsonElement element)
20+
{
21+
Debug.Assert(element.ValueKind is JsonValueKind.Number,
22+
$"Expect JSON element type {JsonValueKind.Number:G}, but found {element.ValueKind:G}.");
23+
24+
Parent = null;
25+
HasElement = true;
26+
Element = element;
27+
_longValue = null;
28+
_decimalValue = null;
29+
_doubleValue = null;
30+
_floatValue = null;
31+
_isValueRead = false;
32+
}
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
public JsonNumber(JsonValue parent, int value)
36+
: this(parent)
37+
{
38+
_longValue = Convert.ToInt64(value);
39+
}
40+
41+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42+
public JsonNumber(JsonValue parent, long value)
43+
: this(parent)
44+
{
45+
_longValue = value;
46+
}
47+
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
public JsonNumber(JsonValue parent, double value)
50+
: this(parent)
51+
{
52+
_doubleValue = value;
53+
}
54+
55+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
56+
public JsonNumber(JsonValue parent, short value)
57+
: this(parent)
58+
{
59+
_longValue = Convert.ToInt64(value);
60+
}
61+
62+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
63+
public JsonNumber(JsonValue parent, decimal value)
64+
: this(parent)
65+
{
66+
_decimalValue = value;
67+
}
68+
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
public JsonNumber(JsonValue parent, byte value)
71+
: this(parent)
72+
{
73+
_longValue = Convert.ToInt64(value);
74+
}
75+
76+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
77+
public JsonNumber(JsonValue parent, float value)
78+
: this(parent)
79+
{
80+
_floatValue = value;
81+
}
82+
83+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
84+
public JsonNumber(JsonValue parent, uint value)
85+
: this(parent)
86+
{
87+
_longValue = Convert.ToInt64(value);
88+
}
89+
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public JsonNumber(JsonValue parent, ushort value)
92+
: this(parent)
93+
{
94+
_longValue = Convert.ToInt64(value);
95+
}
96+
97+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
98+
public JsonNumber(JsonValue parent, ulong value)
99+
: this(parent)
100+
{
101+
_decimalValue = Convert.ToDecimal(value);
102+
}
103+
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
105+
public JsonNumber(JsonValue parent, sbyte value)
106+
: this(parent)
107+
{
108+
_longValue = Convert.ToInt64(value);
109+
}
110+
111+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
112+
private JsonNumber(JsonValue parent)
113+
{
114+
Debug.Assert(parent is not null);
115+
116+
Parent = parent;
117+
HasElement = false;
118+
Element = default;
119+
_longValue = null;
120+
_decimalValue = null;
121+
_doubleValue = null;
122+
_floatValue = null;
123+
_isValueRead = true;
124+
}
125+
126+
public JsonValue? Parent { get; }
127+
public bool HasElement { get; }
128+
129+
private long? LongValue
130+
{
131+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132+
get
133+
{
134+
CreateValue();
135+
return _longValue;
136+
}
137+
}
138+
139+
private decimal? DecimalValue
140+
{
141+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
142+
get
143+
{
144+
CreateValue();
145+
return _decimalValue;
146+
}
147+
}
148+
149+
private double? DoubleValue
150+
{
151+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
152+
get
153+
{
154+
CreateValue();
155+
return _doubleValue;
156+
}
157+
}
158+
159+
private float? FloatValue
160+
{
161+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
162+
get
163+
{
164+
CreateValue();
165+
return _floatValue;
166+
}
167+
}
168+
169+
private void CreateValue()
170+
{
171+
if (_isValueRead)
172+
{
173+
return;
174+
}
175+
176+
if (HasElement)
177+
{
178+
if (Element.TryGetInt64(out var longValue))
179+
{
180+
_longValue = longValue;
181+
}
182+
else if (Element.TryGetDecimal(out var decimalValue))
183+
{
184+
_decimalValue = decimalValue;
185+
}
186+
else if (Element.TryGetDouble(out var doubleValue))
187+
{
188+
_doubleValue = doubleValue;
189+
}
190+
}
191+
192+
_isValueRead = true;
193+
}
194+
195+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
196+
private decimal GetDecimal() => DecimalValue ?? (LongValue.HasValue
197+
? Convert.ToDecimal(LongValue.Value)
198+
: DoubleValue.HasValue
199+
? Convert.ToDecimal(DoubleValue.Value)
200+
: Convert.ToDecimal(FloatValue!.Value));
201+
202+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
203+
private double GetDouble() => DoubleValue ?? (LongValue.HasValue
204+
? Convert.ToDouble(LongValue.Value)
205+
: Convert.ToDouble(FloatValue!.Value));
206+
207+
public int CompareTo(ref JsonNumber another)
208+
{
209+
if (DecimalValue.HasValue || another.DecimalValue.HasValue)
210+
{
211+
return GetDecimal().CompareTo(another.GetDecimal());
212+
}
213+
214+
if (DoubleValue.HasValue || FloatValue.HasValue ||
215+
another.DoubleValue.HasValue || another.FloatValue.HasValue)
216+
{
217+
return JsonValueComparer.CompareDouble(GetDouble(), another.GetDouble());
218+
}
219+
220+
return LongValue!.Value.CompareTo(another.LongValue!.Value);
221+
}
222+
223+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
224+
public bool RawTextEquals(ref JsonNumber another)
225+
{
226+
if (!HasElement || HasElement != another.HasElement)
227+
{
228+
return false;
229+
}
230+
231+
return string.Equals(Element.GetRawText(), another.Element.GetRawText());
232+
}
233+
234+
public static bool TryGetJsonNumber(JsonValue jsonValue, out JsonNumber result)
235+
{
236+
if (jsonValue.TryGetValue<JsonElement>(out var element))
237+
{
238+
if (element.ValueKind is JsonValueKind.Number)
239+
{
240+
result = new JsonNumber(element);
241+
return true;
242+
}
243+
}
244+
else
245+
{
246+
if (jsonValue.TryGetValue<int>(out var intValue))
247+
{
248+
result = new JsonNumber(jsonValue, intValue);
249+
return true;
250+
}
251+
252+
if (jsonValue.TryGetValue<long>(out var longValue))
253+
{
254+
result = new JsonNumber(jsonValue, longValue);
255+
return true;
256+
}
257+
258+
if (jsonValue.TryGetValue<double>(out var doubleValue))
259+
{
260+
result = new JsonNumber(jsonValue, doubleValue);
261+
return true;
262+
}
263+
264+
if (jsonValue.TryGetValue<short>(out var shortValue))
265+
{
266+
result = new JsonNumber(jsonValue, shortValue);
267+
return true;
268+
}
269+
270+
if (jsonValue.TryGetValue<decimal>(out var decimalValue))
271+
{
272+
result = new JsonNumber(jsonValue, decimalValue);
273+
return true;
274+
}
275+
276+
if (jsonValue.TryGetValue<byte>(out var byteValue))
277+
{
278+
result = new JsonNumber(jsonValue, byteValue);
279+
return true;
280+
}
281+
282+
if (jsonValue.TryGetValue<float>(out var floatValue))
283+
{
284+
result = new JsonNumber(jsonValue, floatValue);
285+
return true;
286+
}
287+
288+
if (jsonValue.TryGetValue<uint>(out var uintValue))
289+
{
290+
result = new JsonNumber(jsonValue, uintValue);
291+
return true;
292+
}
293+
294+
if (jsonValue.TryGetValue<ushort>(out var ushortValue))
295+
{
296+
result = new JsonNumber(jsonValue, ushortValue);
297+
return true;
298+
}
299+
300+
if (jsonValue.TryGetValue<ulong>(out var ulongValue))
301+
{
302+
result = new JsonNumber(jsonValue, ulongValue);
303+
return true;
304+
}
305+
306+
if (jsonValue.TryGetValue<sbyte>(out var sbyteValue))
307+
{
308+
result = new JsonNumber(jsonValue, sbyteValue);
309+
return true;
310+
}
311+
}
312+
313+
result = default;
314+
return false;
315+
}
316+
}
317+
}

‎src/SystemTextJson.JsonDiffPatch/JsonString.cs

+402
Large diffs are not rendered by default.

‎src/SystemTextJson.JsonDiffPatch/JsonValueComparer.Number.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private static bool AreDoubleClose(double x, double y)
2020
return false;
2121
}
2222

23-
private static int CompareDouble(double x, double y)
23+
internal static int CompareDouble(double x, double y)
2424
{
2525
return AreDoubleClose(x, y) ? 0 : x.CompareTo(y);
2626
}

‎src/SystemTextJson.JsonDiffPatch/JsonValueComparer.String.cs

+1-50
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,8 @@
22
{
33
static partial class JsonValueComparer
44
{
5-
private static int CompareDateTime(ref JsonValueComparisonContext x, ref JsonValueComparisonContext y)
5+
internal static int CompareByteArray(byte[] x, byte[] y)
66
{
7-
if (x.ValueType == typeof(DateTime))
8-
{
9-
return x.GetDateTime().CompareTo(y.GetDateTime());
10-
}
11-
12-
return x.GetDateTimeOffset().CompareTo(y.GetDateTimeOffset());
13-
}
14-
15-
private static bool TryCompareByteArray(ref JsonValueComparisonContext x, ref JsonValueComparisonContext y,
16-
out int result)
17-
{
18-
if (x.ValueType == typeof(byte[]))
19-
{
20-
if (y.TryGetByteArray(out var byteArrayY))
21-
{
22-
result = CompareByteArray(x.GetByteArray(), byteArrayY);
23-
return true;
24-
}
25-
}
26-
else if (y.ValueType == typeof(byte[]))
27-
{
28-
if (x.TryGetByteArray(out var byteArrayX))
29-
{
30-
result = CompareByteArray(byteArrayX, y.GetByteArray());
31-
return true;
32-
}
33-
}
34-
35-
result = -1;
36-
return false;
37-
}
38-
39-
private static int CompareByteArray(byte[]? x, byte[]? y)
40-
{
41-
if (x is null && y is null)
42-
{
43-
return 0;
44-
}
45-
46-
if (x is null)
47-
{
48-
return -1;
49-
}
50-
51-
if (y is null)
52-
{
53-
return 1;
54-
}
55-
567
if (x.Length == 0 && y.Length == 0)
578
{
589
return 0;

‎src/SystemTextJson.JsonDiffPatch/JsonValueComparer.cs

+4-79
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Diagnostics;
2-
using System.Text.Json.Nodes;
1+
using System.Text.Json.Nodes;
32

43
namespace System.Text.Json.JsonDiffPatch
54
{
@@ -30,84 +29,10 @@ public static int Compare(JsonValue? x, JsonValue? y)
3029
return 1;
3130
}
3231

33-
var contextX = new JsonValueComparisonContext(x);
34-
var contextY = new JsonValueComparisonContext(y);
32+
var wrapperX = new JsonValueWrapper(x);
33+
var wrapperY = new JsonValueWrapper(y);
3534

36-
if (contextX.ValueKind != contextY.ValueKind)
37-
{
38-
return -((int) contextX.ValueKind - (int) contextY.ValueKind);
39-
}
40-
41-
return Compare(contextX.ValueKind, ref contextX, ref contextY);
42-
}
43-
44-
internal static int Compare(JsonValueKind valueKind, ref JsonValueComparisonContext x,
45-
ref JsonValueComparisonContext y)
46-
{
47-
Debug.Assert(x.Value is not null);
48-
Debug.Assert(y.Value is not null);
49-
Debug.Assert(x.ValueKind == y.ValueKind);
50-
51-
switch (valueKind)
52-
{
53-
case JsonValueKind.Number:
54-
if (x.ValueType == typeof(decimal) || y.ValueType == typeof(decimal) ||
55-
x.ValueType == typeof(ulong) || y.ValueType == typeof(ulong))
56-
{
57-
return x.GetDecimal().CompareTo(y.GetDecimal());
58-
}
59-
60-
if (x.ValueType == typeof(double) || y.ValueType == typeof(double) ||
61-
x.ValueType == typeof(float) || y.ValueType == typeof(float))
62-
{
63-
return CompareDouble(x.GetDouble(), y.GetDouble());
64-
}
65-
66-
return x.GetInt64().CompareTo(y.GetInt64());
67-
68-
case JsonValueKind.String:
69-
if (x.StringValueKind == y.StringValueKind)
70-
{
71-
switch (x.StringValueKind)
72-
{
73-
case JsonStringValueKind.DateTime:
74-
return CompareDateTime(ref x, ref y);
75-
case JsonStringValueKind.Guid:
76-
return x.GetGuid().CompareTo(y.GetGuid());
77-
case JsonStringValueKind.String:
78-
if ((x.ValueType == typeof(string) || x.ValueType == typeof(char))
79-
&& (y.ValueType == typeof(string) || y.ValueType == typeof(char)))
80-
{
81-
return StringComparer.Ordinal.Compare(x.GetString(), y.GetString());
82-
}
83-
// Because testing whether string is based64 encoded is expensive operation,
84-
// we only test it when comparing with a byte array. Otherwise, we compare raw text.
85-
else if (x.ValueType == typeof(byte[]) || y.ValueType == typeof(byte[]))
86-
{
87-
if (TryCompareByteArray(ref x, ref y, out var compareResult))
88-
{
89-
return compareResult;
90-
}
91-
}
92-
93-
break;
94-
}
95-
}
96-
97-
return StringComparer.Ordinal.Compare(x.GetRawText(), y.GetRawText());
98-
99-
case JsonValueKind.Null:
100-
case JsonValueKind.False:
101-
case JsonValueKind.True:
102-
return 0;
103-
104-
case JsonValueKind.Undefined:
105-
case JsonValueKind.Object:
106-
case JsonValueKind.Array:
107-
default:
108-
throw new ArgumentOutOfRangeException(
109-
nameof(valueKind), $"Unexpected value kind {valueKind:G}");
110-
}
35+
return wrapperX.CompareTo(ref wrapperY);
11136
}
11237
}
11338
}

‎src/SystemTextJson.JsonDiffPatch/JsonValueComparisonContext.cs

-599
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System.Text.Json.Nodes;
2+
3+
namespace System.Text.Json.JsonDiffPatch
4+
{
5+
/// <summary>
6+
/// Wrapper of <see cref="JsonNode"/> for value comparison purpose.
7+
/// </summary>
8+
internal struct JsonValueWrapper
9+
{
10+
// Keep as fields to avoid copy
11+
public JsonNumber NumberValue;
12+
public JsonString StringValue;
13+
14+
public JsonValueWrapper(JsonValue value)
15+
{
16+
Value = value;
17+
ValueKind = JsonValueKind.Undefined;
18+
StringValue = default;
19+
20+
if (JsonNumber.TryGetJsonNumber(value, out NumberValue))
21+
{
22+
ValueKind = JsonValueKind.Number;
23+
}
24+
else if (JsonString.TryGetJsonString(value, out StringValue))
25+
{
26+
ValueKind = JsonValueKind.String;
27+
}
28+
else if (value.TryGetValue<bool>(out var booleanValue))
29+
{
30+
ValueKind = booleanValue ? JsonValueKind.True : JsonValueKind.False;
31+
}
32+
}
33+
34+
public JsonValueKind ValueKind { get; }
35+
public JsonValue Value { get; }
36+
37+
public bool DeepEquals(ref JsonValueWrapper another, in JsonComparerOptions comparerOptions)
38+
{
39+
var valueComparer = comparerOptions.ValueComparer;
40+
if (valueComparer is not null)
41+
{
42+
var hash1 = valueComparer.GetHashCode(Value);
43+
var hash2 = valueComparer.GetHashCode(another.Value);
44+
45+
if (hash1 != hash2)
46+
{
47+
return false;
48+
}
49+
50+
return valueComparer.Equals(Value, another.Value);
51+
}
52+
53+
return DeepEquals(ref another, comparerOptions.JsonElementComparison);
54+
}
55+
56+
public bool DeepEquals(ref JsonValueWrapper another, JsonElementComparison jsonElementComparison)
57+
{
58+
if (ReferenceEquals(Value, another.Value))
59+
{
60+
return true;
61+
}
62+
63+
if (ValueKind != another.ValueKind)
64+
{
65+
return false;
66+
}
67+
68+
switch (ValueKind)
69+
{
70+
case JsonValueKind.Number:
71+
if (jsonElementComparison is JsonElementComparison.RawText &&
72+
NumberValue.HasElement &&
73+
another.NumberValue.HasElement)
74+
{
75+
return NumberValue.RawTextEquals(ref another.NumberValue);
76+
}
77+
78+
return NumberValue.CompareTo(ref another.NumberValue) == 0;
79+
80+
case JsonValueKind.String:
81+
if (jsonElementComparison is JsonElementComparison.RawText &&
82+
StringValue.HasElement &&
83+
another.StringValue.HasElement)
84+
{
85+
return StringValue.ValueEquals(ref another.StringValue);
86+
}
87+
88+
return StringValue.Equals(ref another.StringValue);
89+
90+
case JsonValueKind.True:
91+
case JsonValueKind.False:
92+
return true;
93+
94+
case JsonValueKind.Null:
95+
case JsonValueKind.Undefined:
96+
case JsonValueKind.Object:
97+
case JsonValueKind.Array:
98+
default:
99+
return Value.TryGetValue<object>(out var objX)
100+
&& another.Value.TryGetValue<object>(out var objY)
101+
&& Equals(objX, objY);
102+
}
103+
}
104+
105+
public int CompareTo(ref JsonValueWrapper another)
106+
{
107+
if (ValueKind != another.ValueKind)
108+
{
109+
return -((int) ValueKind - (int) another.ValueKind);
110+
}
111+
112+
switch (ValueKind)
113+
{
114+
case JsonValueKind.Number:
115+
return NumberValue.CompareTo(ref another.NumberValue);
116+
117+
case JsonValueKind.String:
118+
return StringValue.CompareTo(ref another.StringValue);
119+
120+
case JsonValueKind.True:
121+
case JsonValueKind.False:
122+
return 0;
123+
124+
case JsonValueKind.Null:
125+
case JsonValueKind.Undefined:
126+
case JsonValueKind.Object:
127+
case JsonValueKind.Array:
128+
default:
129+
throw new ArgumentOutOfRangeException(
130+
nameof(ValueKind), $"Unexpected value kind {ValueKind:G}");
131+
}
132+
}
133+
}
134+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
using System.Runtime.CompilerServices;
22

3-
[assembly:InternalsVisibleTo("SystemTextJson.JsonDiffPatch.Benchmark")]
3+
[assembly:InternalsVisibleTo("SystemTextJson.JsonDiffPatch.Benchmark")]
4+
[assembly:InternalsVisibleTo("SystemTextJson.JsonDiffPatch.UnitTests")]
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Text.Json.JsonDiffPatch;
1+
using System.Text.Json;
2+
using System.Text.Json.JsonDiffPatch;
23
using System.Text.Json.Nodes;
34
using BenchmarkDotNet.Attributes;
45
using Newtonsoft.Json.Linq;
@@ -8,21 +9,30 @@ namespace SystemTextJson.JsonDiffPatch.Benchmark
89
public class DeepEqualsJsonFileBenchmark : JsonFileBenchmark
910
{
1011
[Benchmark]
11-
public bool SystemTextJson()
12+
public bool SystemTextJson_Node()
1213
{
13-
var node1 = JsonNode.Parse(JsonLeft);
14-
var node2 = JsonNode.Parse(JsonLeft);
14+
var json1 = JsonNode.Parse(JsonLeft);
15+
var json2 = JsonNode.Parse(JsonLeft);
1516

16-
return node1.DeepEquals(node2, JsonElementComparison.Semantic);
17+
return json1.DeepEquals(json2, JsonElementComparison.Semantic);
18+
}
19+
20+
[Benchmark]
21+
public bool SystemTextJson_Document()
22+
{
23+
using var json1 = JsonDocument.Parse(JsonLeft);
24+
using var json2 = JsonDocument.Parse(JsonLeft);
25+
26+
return json1.DeepEquals(json2, JsonElementComparison.Semantic);
1727
}
1828

1929
[Benchmark]
2030
public bool JsonNet()
2131
{
22-
var token1 = JToken.Parse(JsonLeft);
23-
var token2 = JToken.Parse(JsonLeft);
32+
var json1 = JToken.Parse(JsonLeft);
33+
var json2 = JToken.Parse(JsonLeft);
2434

25-
return JToken.DeepEquals(token1, token2);
35+
return JToken.DeepEquals(json1, json2);
2636
}
2737
}
2838
}

‎test/SystemTextJson.JsonDiffPatch.Benchmark/JsonNodeHelper.cs ‎test/SystemTextJson.JsonDiffPatch.Benchmark/JsonHelper.cs

+31-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
2+
using System.Buffers;
3+
using System.Text;
24
using System.Text.Json;
35
using System.Text.Json.Nodes;
46

57
namespace SystemTextJson.JsonDiffPatch.Benchmark
68
{
7-
public static class JsonNodeHelper
9+
internal static class JsonHelper
810
{
911
public static JsonNode? Parse(string json)
1012
{
@@ -66,5 +68,32 @@ public static class JsonNodeHelper
6668

6769
throw new ArgumentException("Cannot parse JSON element.");
6870
}
71+
72+
#if NET
73+
public static JsonElementWrapper ParseElement(string json)
74+
{
75+
var jsonChars = json.AsSpan();
76+
var byteCount = Encoding.UTF8.GetByteCount(jsonChars);
77+
var buffer = ArrayPool<byte>.Shared.Rent(byteCount);
78+
var length = Encoding.UTF8.GetBytes(jsonChars, buffer);
79+
var reader = new Utf8JsonReader(buffer.AsSpan().Slice(0, length));
80+
return new JsonElementWrapper
81+
{
82+
Value = JsonElement.ParseValue(ref reader),
83+
Buffer = buffer
84+
};
85+
}
86+
87+
public ref struct JsonElementWrapper
88+
{
89+
public JsonElement Value;
90+
public byte[] Buffer;
91+
92+
public void Dispose()
93+
{
94+
ArrayPool<byte>.Shared.Return(Buffer, true);
95+
}
96+
}
97+
#endif
6998
}
70-
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Text.Json;
2+
using System.Text.Json.JsonDiffPatch;
3+
using System.Text.Json.Nodes;
4+
using BenchmarkDotNet.Attributes;
5+
using Newtonsoft.Json.Linq;
6+
7+
namespace SystemTextJson.JsonDiffPatch.Benchmark
8+
{
9+
[IterationCount(10)]
10+
public class QuickDeepEquals : JsonFileBenchmark
11+
{
12+
[Params(JsonFileSize.Small)]
13+
public override JsonFileSize FileSize { get; set; }
14+
15+
[Benchmark]
16+
public bool SystemTextJson()
17+
{
18+
var node1 = JsonNode.Parse(JsonLeft);
19+
var node2 = JsonNode.Parse(JsonLeft);
20+
21+
return node1.DeepEquals(node2, JsonElementComparison.Semantic);
22+
}
23+
24+
[Benchmark]
25+
public bool SystemTextJson_Document()
26+
{
27+
var node1 = JsonDocument.Parse(JsonLeft);
28+
var node2 = JsonDocument.Parse(JsonLeft);
29+
30+
return node1.DeepEquals(node2, JsonElementComparison.Semantic);
31+
}
32+
33+
[Benchmark]
34+
public bool SystemTextJson_Document_RawText()
35+
{
36+
var node1 = JsonDocument.Parse(JsonLeft);
37+
var node2 = JsonDocument.Parse(JsonLeft);
38+
39+
return node1.DeepEquals(node2);
40+
}
41+
42+
[Benchmark]
43+
public JToken JsonNet()
44+
{
45+
var token1 = JToken.Parse(JsonLeft);
46+
var token2 = JToken.Parse(JsonLeft);
47+
48+
return JToken.DeepEquals(token1, token2);
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text.Json.JsonDiffPatch;
2+
using System.Text.Json.Nodes;
3+
using BenchmarkDotNet.Attributes;
4+
using Newtonsoft.Json.Linq;
5+
6+
namespace SystemTextJson.JsonDiffPatch.Benchmark
7+
{
8+
[IterationCount(10)]
9+
public class QuickDiff : JsonFileBenchmark
10+
{
11+
[Params(JsonFileSize.Small)]
12+
public override JsonFileSize FileSize { get; set; }
13+
14+
[Benchmark]
15+
public JsonNode? SystemTextJson()
16+
{
17+
var node1 = JsonNode.Parse(JsonLeft);
18+
var node2 = JsonNode.Parse(JsonRight);
19+
20+
return node1.Diff(node2, BenchmarkHelper.CreateDiffOptionsWithJsonNetMatch());
21+
}
22+
23+
[Benchmark]
24+
public JToken JsonNet()
25+
{
26+
var token1 = JToken.Parse(JsonLeft);
27+
var token2 = JToken.Parse(JsonRight);
28+
29+
return BenchmarkHelper.CreateJsonNetDiffPatch().Diff(token1, token2);
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests
6+
{
7+
[SuppressMessage("ReSharper", "HeapView.BoxingAllocation")]
8+
public static class DeepEqualsTestData
9+
{
10+
public static IEnumerable<object?[]> RawTextEqual(Func<string?, object?> jsonizer)
11+
{
12+
yield return new[] {jsonizer("true"), jsonizer("true"), true};
13+
yield return new[] {jsonizer("true"), jsonizer("false"), false};
14+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"2019-11-27\""), true};
15+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"2019-11-27T00:00:00.000\""), false};
16+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"Shaun is a rabbit\""), false};
17+
yield return new[] {jsonizer("1"), jsonizer("1"), true};
18+
yield return new[] {jsonizer("1"), jsonizer("2"), false};
19+
yield return new[] {jsonizer("1.0"), jsonizer("1"), false};
20+
yield return new[] {jsonizer("1.12e1"), jsonizer("11.2"), false};
21+
yield return new[] {jsonizer("-1"), jsonizer("-1"), true};
22+
yield return new[] {jsonizer("-1"), jsonizer("-1.0"), false};
23+
yield return new[] {jsonizer("-1.1e1"), jsonizer("-11"), false};
24+
yield return new[]
25+
{
26+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
27+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
28+
true
29+
};
30+
yield return new[]
31+
{
32+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
33+
jsonizer("\"b8baf656-8e97-4694-ae1a-be35e3a86db5\""),
34+
false
35+
};
36+
yield return new[]
37+
{
38+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
39+
jsonizer("\"9D423BBA-B9A8-4D19-A39A-B421BED58E02\""),
40+
false
41+
};
42+
yield return new[] {jsonizer("\"Shaun is a rabbit\""), jsonizer("\"Shaun is a rabbit\""), true};
43+
yield return new[] {jsonizer("\"Shaun is a rabbit\""), jsonizer("\"Shawn is a rabbit\""), false};
44+
yield return new[] {jsonizer("1"), jsonizer("\"Shaun is a rabbit\""), false};
45+
}
46+
47+
public static IEnumerable<object?[]> SemanticEqual(Func<string?, object?> jsonizer)
48+
{
49+
yield return new[] {jsonizer("true"), jsonizer("true"), true};
50+
yield return new[] {jsonizer("true"), jsonizer("false"), false};
51+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"2019-11-27\""), true};
52+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"2019-11-27T00:00:00.000\""), true};
53+
yield return new[] {jsonizer("\"2019-11-27\""), jsonizer("\"Shaun is a rabbit\""), false};
54+
yield return new[] {jsonizer("1"), jsonizer("1"), true};
55+
yield return new[] {jsonizer("1"), jsonizer("2"), false};
56+
yield return new[] {jsonizer("1.0"), jsonizer("1"), true};
57+
yield return new[] {jsonizer("1.12e1"), jsonizer("11.2"), true};
58+
yield return new[] {jsonizer("-1"), jsonizer("-1"), true};
59+
yield return new[] {jsonizer("-1"), jsonizer("-1.0"), true};
60+
yield return new[] {jsonizer("-1.1e1"), jsonizer("-11"), true};
61+
yield return new[]
62+
{
63+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
64+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
65+
true
66+
};
67+
yield return new[]
68+
{
69+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
70+
jsonizer("\"b8baf656-8e97-4694-ae1a-be35e3a86db5\""),
71+
false
72+
};
73+
yield return new[]
74+
{
75+
jsonizer("\"9d423bba-b9a8-4d19-a39a-b421bed58e02\""),
76+
jsonizer("\"9D423BBA-B9A8-4D19-A39A-B421BED58E02\""),
77+
true
78+
};
79+
yield return new[] {jsonizer("\"Shaun is a rabbit\""), jsonizer("\"Shaun is a rabbit\""), true};
80+
yield return new[] {jsonizer("\"Shaun is a rabbit\""), jsonizer("\"Shawn is a rabbit\""), false};
81+
yield return new[] {jsonizer("1"), jsonizer("\"Shaun is a rabbit\""), false};
82+
}
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Text.Json.JsonDiffPatch;
3+
using Xunit;
4+
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests
6+
{
7+
public class DefaultOptionsTests : IDisposable
8+
{
9+
private readonly JsonElementComparison _comparisonMode;
10+
11+
public DefaultOptionsTests()
12+
{
13+
_comparisonMode = JsonDiffPatcher.DefaultComparison;
14+
}
15+
16+
[Fact]
17+
public void DefaultDeepEqualsComparison_ComparerOptions()
18+
{
19+
JsonDiffPatcher.DefaultComparison = JsonElementComparison.Semantic;
20+
21+
JsonComparerOptions comparerOptions = default;
22+
23+
Assert.Equal(JsonDiffPatcher.DefaultComparison, comparerOptions.JsonElementComparison);
24+
}
25+
26+
[Fact]
27+
public void DefaultDeepEqualsComparison_DiffOptions()
28+
{
29+
JsonDiffPatcher.DefaultComparison = JsonElementComparison.Semantic;
30+
31+
var diffOptions = new JsonDiffOptions();
32+
33+
Assert.Equal(JsonDiffPatcher.DefaultComparison, diffOptions.JsonElementComparison);
34+
}
35+
36+
public void Dispose()
37+
{
38+
JsonDiffPatcher.DefaultComparison = _comparisonMode;
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System.Text.Json;
2+
using System.Text.Json.JsonDiffPatch;
3+
using Xunit;
4+
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.DocumentTests
6+
{
7+
public class DeepEqualsTests
8+
{
9+
[Fact]
10+
public void Default()
11+
{
12+
Assert.True(default(JsonDocument).DeepEquals(default));
13+
}
14+
15+
[Fact]
16+
public void Object_Identical()
17+
{
18+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
19+
using var json2 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
20+
21+
Assert.True(json1.DeepEquals(json2));
22+
}
23+
24+
[Fact]
25+
public void Object_Whitespace()
26+
{
27+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
28+
using var json2 = JsonDocument.Parse("{ \"foo\": \"bar\", \"baz\":\"qux\" }");
29+
30+
Assert.True(json1.DeepEquals(json2));
31+
}
32+
33+
[Fact]
34+
public void Object_PropertyOrdering()
35+
{
36+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
37+
using var json2 = JsonDocument.Parse("{\"baz\":\"qux\",\"foo\":\"bar\"}");
38+
39+
Assert.True(json1.DeepEquals(json2));
40+
}
41+
42+
[Fact]
43+
public void Object_PropertyValue()
44+
{
45+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
46+
using var json2 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"quz\"}");
47+
48+
Assert.False(json1.DeepEquals(json2));
49+
}
50+
51+
[Fact]
52+
public void Object_MissingProperty()
53+
{
54+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
55+
using var json2 = JsonDocument.Parse("{\"foo\":\"bar\"}");
56+
57+
Assert.False(json1.DeepEquals(json2));
58+
}
59+
60+
[Fact]
61+
public void Object_ExtraProperty()
62+
{
63+
using var json1 = JsonDocument.Parse("{\"foo\":\"bar\"}");
64+
using var json2 = JsonDocument.Parse("{\"foo\":\"bar\",\"baz\":\"qux\"}");
65+
66+
Assert.False(json1.DeepEquals(json2));
67+
}
68+
69+
[Fact]
70+
public void Array_Identical()
71+
{
72+
using var json1 = JsonDocument.Parse("[1,2,3]");
73+
using var json2 = JsonDocument.Parse("[1,2,3]");
74+
75+
Assert.True(json1.DeepEquals(json2));
76+
}
77+
78+
[Fact]
79+
public void Array_Whitespace()
80+
{
81+
using var json1 = JsonDocument.Parse("[1,2,3]");
82+
using var json2 = JsonDocument.Parse("[ 1, 2, 3 ]");
83+
84+
Assert.True(json1.DeepEquals(json2));
85+
}
86+
87+
[Fact]
88+
public void Array_ItemOrdering()
89+
{
90+
using var json1 = JsonDocument.Parse("[1,2,3]");
91+
using var json2 = JsonDocument.Parse("[1,3,2]");
92+
93+
Assert.False(json1.DeepEquals(json2));
94+
}
95+
96+
[Fact]
97+
public void Array_ItemValue()
98+
{
99+
using var json1 = JsonDocument.Parse("[1,2,3]");
100+
using var json2 = JsonDocument.Parse("[1,2,5]");
101+
102+
Assert.False(json1.DeepEquals(json2));
103+
}
104+
105+
[Fact]
106+
public void Array_MissingItem()
107+
{
108+
using var json1 = JsonDocument.Parse("[1,2,3]");
109+
using var json2 = JsonDocument.Parse("[1,2]");
110+
111+
Assert.False(json1.DeepEquals(json2));
112+
}
113+
114+
[Fact]
115+
public void Array_ExtraItem()
116+
{
117+
using var json1 = JsonDocument.Parse("[1,2]");
118+
using var json2 = JsonDocument.Parse("[1,2,3]");
119+
120+
Assert.False(json1.DeepEquals(json2));
121+
}
122+
123+
[Theory]
124+
[MemberData(nameof(DocumentTestData.RawTextEqual), MemberType = typeof(DocumentTestData))]
125+
public void Value_RawText(JsonDocument json1, JsonDocument json2, bool expected)
126+
{
127+
using (json1)
128+
{
129+
using (json2)
130+
{
131+
Assert.Equal(expected, json1.DeepEquals(json2));
132+
}
133+
}
134+
}
135+
136+
[Theory]
137+
[MemberData(nameof(DocumentTestData.SemanticEqual), MemberType = typeof(DocumentTestData))]
138+
public void Value_Semantic(JsonDocument json1, JsonDocument json2, bool expected)
139+
{
140+
using (json1)
141+
{
142+
using (json2)
143+
{
144+
Assert.Equal(expected, json1.DeepEquals(json2, JsonElementComparison.Semantic));
145+
}
146+
}
147+
}
148+
}
149+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json;
3+
4+
namespace SystemTextJson.JsonDiffPatch.UnitTests.DocumentTests
5+
{
6+
public class DocumentTestData
7+
{
8+
public static IEnumerable<object?[]> RawTextEqual => DeepEqualsTestData.RawTextEqual(Json);
9+
10+
public static IEnumerable<object?[]> SemanticEqual => DeepEqualsTestData.SemanticEqual(Json);
11+
12+
private static JsonDocument? Json(string? jsonValue)
13+
{
14+
return jsonValue is null ? null : JsonDocument.Parse($"{jsonValue}");
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System.Text.Json;
2+
using System.Text.Json.JsonDiffPatch;
3+
using Xunit;
4+
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.ElementTests
6+
{
7+
public class DeepEqualsTests
8+
{
9+
[Fact]
10+
public void Default()
11+
{
12+
Assert.True(default(JsonElement).DeepEquals(default));
13+
}
14+
15+
[Fact]
16+
public void Object_Identical()
17+
{
18+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
19+
var json2 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
20+
21+
Assert.True(json1.DeepEquals(json2));
22+
}
23+
24+
[Fact]
25+
public void Object_Whitespace()
26+
{
27+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
28+
var json2 = JsonSerializer.Deserialize<JsonElement>("{ \"foo\": \"bar\", \"baz\":\"qux\" }");
29+
30+
Assert.True(json1.DeepEquals(json2));
31+
}
32+
33+
[Fact]
34+
public void Object_PropertyOrdering()
35+
{
36+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
37+
var json2 = JsonSerializer.Deserialize<JsonElement>("{\"baz\":\"qux\",\"foo\":\"bar\"}");
38+
39+
Assert.True(json1.DeepEquals(json2));
40+
}
41+
42+
[Fact]
43+
public void Object_PropertyValue()
44+
{
45+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
46+
var json2 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"quz\"}");
47+
48+
Assert.False(json1.DeepEquals(json2));
49+
}
50+
51+
[Fact]
52+
public void Object_MissingProperty()
53+
{
54+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
55+
var json2 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\"}");
56+
57+
Assert.False(json1.DeepEquals(json2));
58+
}
59+
60+
[Fact]
61+
public void Object_ExtraProperty()
62+
{
63+
var json1 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\"}");
64+
var json2 = JsonSerializer.Deserialize<JsonElement>("{\"foo\":\"bar\",\"baz\":\"qux\"}");
65+
66+
Assert.False(json1.DeepEquals(json2));
67+
}
68+
69+
[Fact]
70+
public void Array_Identical()
71+
{
72+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
73+
var json2 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
74+
75+
Assert.True(json1.DeepEquals(json2));
76+
}
77+
78+
[Fact]
79+
public void Array_Whitespace()
80+
{
81+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
82+
var json2 = JsonSerializer.Deserialize<JsonElement>("[ 1, 2, 3 ]");
83+
84+
Assert.True(json1.DeepEquals(json2));
85+
}
86+
87+
[Fact]
88+
public void Array_ItemOrdering()
89+
{
90+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
91+
var json2 = JsonSerializer.Deserialize<JsonElement>("[1,3,2]");
92+
93+
Assert.False(json1.DeepEquals(json2));
94+
}
95+
96+
[Fact]
97+
public void Array_ItemValue()
98+
{
99+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
100+
var json2 = JsonSerializer.Deserialize<JsonElement>("[1,2,5]");
101+
102+
Assert.False(json1.DeepEquals(json2));
103+
}
104+
105+
[Fact]
106+
public void Array_MissingItem()
107+
{
108+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
109+
var json2 = JsonSerializer.Deserialize<JsonElement>("[1,2]");
110+
111+
Assert.False(json1.DeepEquals(json2));
112+
}
113+
114+
[Fact]
115+
public void Array_ExtraItem()
116+
{
117+
var json1 = JsonSerializer.Deserialize<JsonElement>("[1,2]");
118+
var json2 = JsonSerializer.Deserialize<JsonElement>("[1,2,3]");
119+
120+
Assert.False(json1.DeepEquals(json2));
121+
}
122+
123+
[Theory]
124+
[MemberData(nameof(ElementTestData.RawTextEqual), MemberType = typeof(ElementTestData))]
125+
public void Value_RawText(JsonElement json1, JsonElement json2, bool expected)
126+
{
127+
Assert.Equal(expected, json1.DeepEquals(json2));
128+
}
129+
130+
[Theory]
131+
[MemberData(nameof(ElementTestData.SemanticEqual), MemberType = typeof(ElementTestData))]
132+
public void Value_Semantic(JsonElement json1, JsonElement json2, bool expected)
133+
{
134+
Assert.Equal(expected, json1.DeepEquals(json2, JsonElementComparison.Semantic));
135+
}
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json;
3+
4+
namespace SystemTextJson.JsonDiffPatch.UnitTests.ElementTests
5+
{
6+
public class ElementTestData
7+
{
8+
public static IEnumerable<object?[]> RawTextEqual => DeepEqualsTestData.RawTextEqual(Json);
9+
10+
public static IEnumerable<object?[]> SemanticEqual => DeepEqualsTestData.SemanticEqual(Json);
11+
12+
private static object? Json(string? jsonValue)
13+
{
14+
// ReSharper disable once HeapView.BoxingAllocation
15+
return jsonValue is null
16+
? default
17+
: JsonSerializer.Deserialize<JsonElement>($"{jsonValue}");
18+
}
19+
}
20+
}

‎test/SystemTextJson.JsonDiffPatch.UnitTests/Formatters/JsonPatchDeltaFormatterTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/FormatterTests/JsonPatchDeltaFormatterTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Text.Json.Nodes;
55
using Xunit;
66

7-
namespace SystemTextJson.JsonDiffPatch.UnitTests.Formatters
7+
namespace SystemTextJson.JsonDiffPatch.UnitTests.FormatterTests
88
{
99
public class JsonPatchDeltaFormatterTests
1010
{
@@ -162,4 +162,4 @@ public void JsonPointer_EscapeSpecialChar()
162162
Assert.Equal("[{\"op\":\"replace\",\"path\":\"/data/~1/~01\",\"value\":2}]", diff!.ToJsonString());
163163
}
164164
}
165-
}
165+
}

‎test/SystemTextJson.JsonDiffPatch.UnitTests/DiffTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/DiffTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Text.Json.Nodes;
33
using Xunit;
44

5-
namespace SystemTextJson.JsonDiffPatch.UnitTests
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.NodeTests
66
{
77
public class DiffTests
88
{

‎test/SystemTextJson.JsonDiffPatch.UnitTests/JsonElementDeepEqualsTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/ElementDeepEqualsTests.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using System.Text.Json.JsonDiffPatch;
22
using System.Text.Json.Nodes;
3-
using SystemTextJson.JsonDiffPatch.UnitTests.TestData;
43
using Xunit;
54

6-
namespace SystemTextJson.JsonDiffPatch.UnitTests
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.NodeTests
76
{
8-
public class JsonElementDeepEqualsTests
7+
public class ElementDeepEqualsTests
98
{
109
[Fact]
1110
public void Object_Identical()
@@ -116,21 +115,21 @@ public void Array_ExtraItem()
116115
}
117116

118117
[Theory]
119-
[MemberData(nameof(JsonValueTestData.ElementRawTextEqual), MemberType = typeof(JsonValueTestData))]
118+
[MemberData(nameof(NodeTestData.ElementRawTextEqual), MemberType = typeof(NodeTestData))]
120119
public void Value_RawText(JsonValue json1, JsonValue json2, bool expected)
121120
{
122121
Assert.Equal(expected, json1.DeepEquals(json2));
123122
}
124123

125124
[Theory]
126-
[MemberData(nameof(JsonValueTestData.ElementSemanticEqual), MemberType = typeof(JsonValueTestData))]
125+
[MemberData(nameof(NodeTestData.ElementSemanticEqual), MemberType = typeof(NodeTestData))]
127126
public void Value_Semantic(JsonValue json1, JsonValue json2, bool expected)
128127
{
129128
Assert.Equal(expected, json1.DeepEquals(json2, JsonElementComparison.Semantic));
130129
}
131130

132131
[Theory]
133-
[MemberData(nameof(JsonValueTestData.ElementObjectSemanticEqual), MemberType = typeof(JsonValueTestData))]
132+
[MemberData(nameof(NodeTestData.ElementObjectSemanticEqual), MemberType = typeof(NodeTestData))]
134133
public void Value_ElementObjectSemanticEqual(JsonValue json1, JsonValue json2, bool expected)
135134
{
136135
Assert.Equal(expected, json1.DeepEquals(json2));

‎test/SystemTextJson.JsonDiffPatch.UnitTests/JsonValueComparerTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/JsonValueComparerTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Text.Json.Nodes;
33
using Xunit;
44

5-
namespace SystemTextJson.JsonDiffPatch.UnitTests
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.NodeTests
66
{
77
public class JsonValueComparerTests
88
{
@@ -74,4 +74,4 @@ public void Compare_EqualToZero(string? json1, string? json2)
7474
Assert.Equal(0, result);
7575
}
7676
}
77-
}
77+
}

‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/NodeTestData.cs

+251
Large diffs are not rendered by default.

‎test/SystemTextJson.JsonDiffPatch.UnitTests/ObjectDeepEqualsTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/ObjectDeepEqualsTests.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
using System.Text.Json.JsonDiffPatch;
22
using System.Text.Json.Nodes;
3-
using SystemTextJson.JsonDiffPatch.UnitTests.TestData;
43
using Xunit;
54

6-
namespace SystemTextJson.JsonDiffPatch.UnitTests
5+
namespace SystemTextJson.JsonDiffPatch.UnitTests.NodeTests
76
{
87
public class ObjectDeepEqualsTests
98
{
9+
[Fact]
10+
public void Default()
11+
{
12+
Assert.True(default(JsonNode).DeepEquals(default));
13+
}
14+
1015
[Fact]
1116
public void Object_Identical()
1217
{
@@ -136,7 +141,7 @@ public void Array_ExtraItem()
136141
}
137142

138143
[Theory]
139-
[MemberData(nameof(JsonValueTestData.ObjectSemanticEqual), MemberType = typeof(JsonValueTestData))]
144+
[MemberData(nameof(NodeTestData.ObjectSemanticEqual), MemberType = typeof(NodeTestData))]
140145
public void Value_ObjectSemanticEqual(JsonValue json1, JsonValue json2, bool expected)
141146
{
142147
Assert.Equal(expected, json1.DeepEquals(json2));

‎test/SystemTextJson.JsonDiffPatch.UnitTests/PatchTests.cs ‎test/SystemTextJson.JsonDiffPatch.UnitTests/NodeTests/PatchTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System.Text.Json.Nodes;
44
using Xunit;
55

6-
namespace SystemTextJson.JsonDiffPatch.UnitTests
6+
namespace SystemTextJson.JsonDiffPatch.UnitTests.NodeTests
77
{
88
public class PatchTests
99
{

‎test/SystemTextJson.JsonDiffPatch.UnitTests/TestData/JsonValueTestData.cs

-324
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.