system-text-json-jsondiffpatch icon indicating copy to clipboard operation
system-text-json-jsondiffpatch copied to clipboard

Any chance to get better nested diff information?

Open Cyber1000 opened this issue 2 years ago • 4 comments

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 avatar Feb 11 '23 13:02 Cyber1000

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

  • ArrayObjectItemKeyFinder
  • ArrayObjectItemMatchByPosition and maybe
  • PropertyFilter

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 avatar Feb 11 '23 20:02 Cyber1000

Did I miss out any documentation about this?

Cyber1000 avatar Feb 11 '23 20:02 Cyber1000

Yes works this way:

  • One suggestion: JsonPatchDeltaFormatter should have overridable strings like PropertyNameOperation and JsonPatchDeltaFormatter.PropertyPathScope should be protected instead of private. I had a small (project-specific) change and needed to copy more than I should.

Cyber1000 avatar Feb 11 '23 22:02 Cyber1000

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:

  • ArrayItemMatcher which deals with the diff context
  • ArrayObjectItemKeyFinder which finds the key/id of an object or array
  • ArrayObjectItemMatchByPosition which indicates whether to compare two objects or arrays by their position

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.

weichch avatar Feb 13 '23 20:02 weichch