Skip to content

Add Patch and Unpatch #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# system-text-json-jsondiffpath
# system-text-json-jsondiffpatch

High-performance, low-allocating JSON objects diff and patch extension for `System.Text.Json`.

Expand All @@ -10,9 +10,9 @@ High-performance, low-allocating JSON objects diff and patch extension for `Syst
- Fast large JSON document diffing with less memory consumption
- Support smart array diffing (e.g. move detect) using LCS and custom array item matcher
- Support diffing long text using [google-diff-match-patch](http://code.google.com/p/google-diff-match-patch/), or write your own diff algorithm
- `JsonNode.Clone` and `JsonNode.DeepEquals` methods
- `JsonNode.DeepClone` and `JsonNode.DeepEquals` methods

- (_Under development_) Patch, unpatch, formatters etc
- (_Under development_) formatters etc

# Install

Expand All @@ -27,27 +27,27 @@ Install-Package SystemTextJson.JsonDiffPatch

```csharp
// Diff JSON files
JsonDocument? diff = JsonDiffPatcher.DiffFile(file1, file2);
JsonNode? diff = JsonDiffPatcher.DiffFile(file1, file2);
// Diff Span<byte>
JsonDocument? diff = JsonDiffPatcher.Diff(span1, span2);
JsonNode? diff = JsonDiffPatcher.Diff(span1, span2);
// Diff streams
JsonDocument? diff = JsonDiffPatcher.Diff(stream1, stream2);
JsonNode? diff = JsonDiffPatcher.Diff(stream1, stream2);
// Diff JSON strings
JsonDocument? diff = JsonDiffPatcher.Diff(json1, json2);
JsonNode? diff = JsonDiffPatcher.Diff(json1, json2);
// Diff JSON readers
JsonDocument? diff = JsonDiffPatcher.Diff(ref reader1, ref reader2);
JsonNode? diff = JsonDiffPatcher.Diff(ref reader1, ref reader2);

// Diff mutable JsonNode objects
var node1 = JsonNode.Parse(...);
var node2 = JsonNode.Parse(...);
JsonNode? diff = node1.Diff(node2);
```

### Clone
### DeepClone

```csharp
var node = JsonNode.Parse(...);
JsonNode? cloned = node.Clone();
JsonNode? cloned = node.DeepClone();
```

### DeepEquals
Expand All @@ -58,6 +58,18 @@ var node2 = JsonNode.Parse(...);
bool equal = node1.DeepEquals(node2);
```

### Patch & Unpatch

```csharp
var node1 = JsonNode.Parse(...);
var node2 = JsonNode.Parse(...);
JsonNode? diff = node1.Diff(node2);
// Patch
JsonDiffPatcher.Patch(ref node1, diff);
// Unpatch
JsonDiffPatcher.ReversePatch(ref node1, diff);
```

### Options

```csharp
Expand All @@ -68,14 +80,6 @@ public struct JsonDiffOptions
/// </summary>
public bool SuppressDetectArrayMove { get; set; }

/// <summary>
/// Specifies whether to include moved item value. Default value is <c>false</c>.
/// </summary>
/// <remarks>
/// See <see link="https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md#array-moves"/>.
/// </remarks>
public bool IncludeValueOnMove { get; set; }

/// <summary>
/// Gets or sets the function to match array items.
/// </summary>
Expand Down Expand Up @@ -148,5 +152,4 @@ Intel Core i7-10750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
| 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 |


_\* 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)_

_\* 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/SimpleDiffBenchmark.cs)_
12 changes: 3 additions & 9 deletions src/SystemTextJson.JsonDiffPatch/Diffs/ArrayItemMatch.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
using System.Text.Json.Nodes;

namespace System.Text.Json.Diffs
namespace System.Text.Json.JsonDiffPatch.Diffs
{
/// <summary>
/// Defines a function that determines whether two items in arrays are equal.
/// </summary>
/// <param name="x">The element in array1.</param>
/// <param name="indexX">The index of <paramref name="x"/> in array1.</param>
/// <param name="y">The element in array2.</param>
/// <param name="indexY">The index of <paramref name="y"/> in array2.</param>
public delegate bool ArrayItemMatch(JsonNode? x, int indexX,
JsonNode? y, int indexY, out bool deepEqual);
/// <param name="context">The comparison context.</param>
public delegate bool ArrayItemMatch(ref ArrayItemMatchContext context);
}
59 changes: 59 additions & 0 deletions src/SystemTextJson.JsonDiffPatch/Diffs/ArrayItemMatchContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Text.Json.Nodes;

namespace System.Text.Json.JsonDiffPatch.Diffs
{
/// <summary>
/// Context for matching array items.
/// </summary>
public struct ArrayItemMatchContext
{
/// <summary>
/// Creates an instance of the context.
/// </summary>
/// <param name="left">The left item.</param>
/// <param name="leftPos">The index of the left item.</param>
/// <param name="right">The right item.</param>
/// <param name="rightPos">The index of the right item.</param>
public ArrayItemMatchContext(JsonNode? left, int leftPos, JsonNode? right, int rightPos)
{
Left = left;
LeftPosition = leftPos;
Right = right;
RightPosition = rightPos;
IsDeepEqual = false;
}

/// <summary>
/// Gets the left item.
/// </summary>
public JsonNode? Left { get; }

/// <summary>
/// Gets the index of the left item.
/// </summary>
public int LeftPosition { get; }

/// <summary>
/// Gets the right item.
/// </summary>
public JsonNode? Right { get; }

/// <summary>
/// Gets the index of the right item.
/// </summary>
public int RightPosition { get; }

/// <summary>
/// Gets whether the result was the two items are deeply equal.
/// </summary>
public bool IsDeepEqual { get; private set; }

/// <summary>
/// Sets <see cref="IsDeepEqual"/> to <c>true</c>.
/// </summary>
public void DeepEqual()
{
IsDeepEqual = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
using System.Text.Json.Nodes;

namespace System.Text.Json.Diffs
namespace System.Text.Json.JsonDiffPatch.Diffs
{
internal readonly struct DefaultArrayItemComparer
{
private readonly JsonDiffOptionsView _options;
private readonly JsonDiffOptions _options;

public DefaultArrayItemComparer(in JsonDiffOptionsView options)
public DefaultArrayItemComparer(JsonDiffOptions options)
{
_options = options;
}

public bool MatchArrayItem(
JsonNode? x, int indexX, JsonNode? y, int indexY,
out bool deepEqual)
public bool MatchArrayItem(ref ArrayItemMatchContext context)
{
// Returns if one JSON node matches another. Nodes are considered match if:
// - they are deeply equal, OR
// - they are of JavaScript object type JsonObject and JsonArray and their
// positions in corresponding arrays are equal
// See: https://github.com/benjamine/jsondiffpatch/blob/a8cde4c666a8a25d09d8f216c7f19397f2e1b569/src/filters/arrays.js#L43

deepEqual = false;

if (_options.PreferFuzzyArrayItemMatch
&& x is JsonObject or JsonArray
&& y is JsonObject or JsonArray)
&& context.Left is JsonObject or JsonArray
&& context.Right is JsonObject or JsonArray)
{
if (FuzzyMatchItem(x, indexX, y, indexY,
out var fuzzyResult, out deepEqual))
if (FuzzyMatchItem(ref context, out var fuzzyResult))
{
return fuzzyResult;
}
}

if (x.DeepEquals(y))
if (context.Left.DeepEquals(context.Right))
{
deepEqual = true;
context.DeepEqual();
return true;
}

if (!_options.PreferFuzzyArrayItemMatch
&& x is JsonObject or JsonArray
&& y is JsonObject or JsonArray)
&& context.Left is JsonObject or JsonArray
&& context.Right is JsonObject or JsonArray)
{
if (FuzzyMatchItem(x, indexX, y, indexY,
out var fuzzyResult, out deepEqual))
if (FuzzyMatchItem(ref context, out var fuzzyResult))
{
return fuzzyResult;
}
Expand All @@ -54,18 +48,15 @@ public bool MatchArrayItem(
return false;
}

private bool FuzzyMatchItem(JsonNode? x, int indexX,
JsonNode? y, int indexY,
out bool result, out bool deepEqual)
private bool FuzzyMatchItem(ref ArrayItemMatchContext context, out bool result)
{
result = false;
deepEqual = false;

var keyFinder = _options.ArrayObjectItemKeyFinder;
if (keyFinder is not null)
{
var keyX = keyFinder(x, indexX);
var keyY = keyFinder(y, indexY);
var keyX = keyFinder(context.Left, context.LeftPosition);
var keyY = keyFinder(context.Right, context.RightPosition);

if (keyX is null && keyY is null)
{
Expand All @@ -79,7 +70,7 @@ private bool FuzzyMatchItem(JsonNode? x, int indexX,

if (_options.ArrayObjectItemMatchByPosition)
{
if (indexX == indexY)
if (context.LeftPosition == context.RightPosition)
{
result = true;
return true;
Expand Down
Loading