wI2L/jsondiff

Unexpected behavior when using `Ignores()` with nested values

hiddeco opened this issue ยท 4 comments

Given the following snippet:

a := map[string]interface{}{}
b := map[string]interface{}{
	"nested": map[string]interface{}{
		"include": true,
		"exclude": false,
	},
}

patch, err := jsondiff.Compare(a, b, jsondiff.Ignores("/nested/exclude"))
if err != nil {
	panic(err)
}
fmt.Printf("%v", patch)

I would expect the patch to equal something like the following:

[{"value":{"include":true},"op":"add","path":"/nested"}]

The actual patch I get presented does however contain the /nested/exclude entry:

[{"value":{"exclude":false,"include":true},"op":"add","path":"/nested"}]

Are my expectations around this feature maybe incorrect, or is this an oversight?

wI2L commented

@hiddeco

The Ignores() option allows to ignore operations for specific operations' paths, not for fields of the document itself.

In other words, in your example, the /nested/exclude is a field of the /nested object, for which the add operation is generated. In this instance, the option would allow you yo ignore the add operation on the /nested path, but not to somehow "mutate" the content of the operation's value to remove the exclude field.


The patch generated in your example has only one add operation since the source document is empty. If you were to do the following instead:

Source:

{"nested":{"exclude":true}}

Dest:

{"nested":{"exclude":false}}

Then the generated patch would lokk like the following:

[{"value":false,"op":"replace","path":"/nested/exclude"}]

In this case, using the jsondiff.Ignores("/nested/exclude") option could be done, since an operation for that path exist.

This is kind of what I suspected to be the case, which means I will likely have to resort to stripping the values from my objects instead of using Ignores().

To explain my use-case: I am not trying to use this library for a Kubernetes Mutating Webhook, but rather to detect differences between existing and desired state. However, in some cases people may want to ignore a difference they introduce in a field of a parent that is optional (and has omitempty set in the JSON representation).

While your argumentation absolutely makes sense! This does present an issue when people initialize their first field in this parent while they also want to ignore this specific field. As the initialization of the parent by itself is an add operation, containing a change that should be ignored.

I will look into alternatives to sort the above, thanks for your prompt response ๐Ÿ™‡

wI2L commented

The Ignores() option is very new and probably not perfect, so there might be something to do to improve it, but I'd like to keep it simple in terms of usage and implementation (no full JSON path support for example, since we're already dealing with the JSON pointer syntax). In your case however, since the desired exclusion is located within the operation's value (du to the fact that the source document is empty it will always generate an add operation), I don't think there's much to do in the lib itself.

However, in some cases people may want to ignore a difference they introduce in a field of a parent that is optional (and has omitempty set in the JSON representation).

You mention the omitempty JSON tag option here, but that's something that I wouldn't like to heave to deal with, and the lib wouldn't be able to, since it only works with an unmarshaled representation of the JSON documents to compare, and as such these kind of information is not available anyway.

I will look into alternatives to sort the above, thanks for your prompt response ๐Ÿ™‡

You're welcome. If you end up finding a solution that fits your needs, don't hesitate to let me know, even if it isn't related to the lib, I'm always interested to get feedback, perhaps this will led to improvements that could be added to the lib itself.

Closing the issue for now, but feel free to re-open or add any further comments in the future.

Went with an approach where both versions of the document are patched with {"op": "remove", "path": "<ignore path>"} using https://github.com/evanphx/json-patch (which is not capable of calculating RFC6902 JSON patches, but can apply them) before doing further comparisons.

Which is a bit awkward due to required conversions, as both libraries have their own Patch type. But it's a small price to pay, and appears to work well for this specific use case.