weichch / system-text-json-jsondiffpatch

High-performance, low-allocating JSON object diff and patch extension for System.Text.Json. Support generating patch document in RFC 6902 JSON Patch format.
MIT License
102 stars 13 forks source link

Any chance to get better nested diff information? #36

Open Cyber1000 opened 1 year ago

Cyber1000 commented 1 year ago

With the following code (excluded the usings for simplicity)

var node1 = JsonNode.Parse("{\"foo\":[ {\"bar1\": \"res1\"} ]}");
var node2 = JsonNode.Parse("{\"foo\":[ {\"bar1\": \"res2\"} ]}");

var x = JsonDiffPatcher.Diff(node1, node2, new JsonPatchDeltaFormatter());
Console.WriteLine(x);

I'm getting follwing:

[
  {
    "op": "remove",
    "path": "/foo/0"
  },
  {
    "op": "add",
    "path": "/foo/0",
    "value": {
      "bar1": "res2"
    }
  }
]

I would like to get something like:

[
  {
    "op": "replace",
    "path": "/foo/0/bar1"
    "value": "res2"
  }
]

I've looked into DefaultFormatter and it seems that JsonDiffDelta has already 2 change entries: one for remove and one for add.

What's the best method to check arrays recursively (the way I want)? Is there some kind of flag or would I need to override FormatArray somehow?

Thanks!

Cyber1000 commented 1 year ago

Seems like I can use following JsonDiffOptions to control array-comparison:

Looks promising, the following works as expected (I've changed bar1 to ID since this would be my real-world example, if for any reason the ID is missing I'm falling back with ArrayObjectItemMatchByPosition=true):

var node1 = JsonNode.Parse("{\"foo\":[ {\"ID\": \"res1\", \"bar2\": \"res3\"} ]}");
var node2 = JsonNode.Parse("{\"foo\":[ {\"ID\": \"res1\", \"bar2\": \"res4\"} ]}");

var opt = new JsonDiffOptions
{
    ArrayObjectItemKeyFinder = ArrayObjectItemKeyFinder,
    ArrayObjectItemMatchByPosition = true
};

object? ArrayObjectItemKeyFinder(JsonNode? node, int index)
{
    if (node is JsonObject obj && obj.TryGetPropertyValue("ID", out var value))
    {
        return value?.GetValue<string>() ?? "";
    }
    return null;
}

var x = JsonDiffPatcher.Diff(node1, node2, new JsonPatchDeltaFormatter(), opt);
Console.WriteLine(x);
Cyber1000 commented 1 year ago

Did I miss out any documentation about this?

Cyber1000 commented 1 year ago

Yes works this way:

weichch commented 1 year ago

What's the best method to check arrays recursively (the way I want)?

I think you've pretty much found the way.

If you have a look at the JsonDiffOptions, there are 3 ways you can use:

By default if the two objects are not deeply equal to each other, then they are considered totally not equal and therefore the remove/add (arguably update) result. To forcibly get a diff of array items, you need to implement an "object comparer" to compare them if they are not deeply equal, similar to an object hash function.

There probably should be some Wiki documentation about array diffs.

JsonPatchDeltaFormatter.PropertyPathScope should be protected instead of private

I agree. The reason for it being a private is because the implementation of the class is actually a combination of partial JSON Pointer and a pointer scope at the moment. The JSON Pointer implementation should ideally be a public type. I did not want to expose this type until it is properly refactored otherwise it might be hard to change the implementation.