java 比较两个 JSON 时忽略特定节点/属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38853598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 03:50:15  来源:igfitidea点击:

Ignore specific nodes/attributes while comparing two JSONs

javajsonjsonassert

提问by Prateik

I want to compare two JSON strings which is a huge hierarchy and want to know where they differ in values. But some values are generated at runtime and are dynamic. I want to ignore those particular nodes from my comparison.

我想比较两个 JSON 字符串,这是一个巨大的层次结构,并想知道它们的值不同之处。但是有些值是在运行时生成的并且是动态的。我想从我的比较中忽略那些特定的节点。

I am currently using JSONAssert from org.SkyScreamerto do the comparison. It gives me nice console output but does not ignore any attributes.

我目前正在使用来自org.SkyScreamer 的 JSONAssert进行比较。它给了我很好的控制台输出,但不会忽略任何属性。

for ex.

例如。

java.lang.AssertionError messageHeader.sentTime
expected:null
got:09082016 18:49:41.123

Now this comes dynamic and should be ignored. Something like

现在这是动态的,应该被忽略。就像是

JSONAssert.assertEquals(expectedJSONString, actualJSONString,JSONCompareMode, *list of attributes to be ignored*)

It would be great if someone suggests a solution in JSONAssert. However other ways are also welcome.

如果有人在 JSONAssert 中提出解决方案,那就太好了。但是也欢迎其他方式。

回答by dknaus

You can use Customization for this. For example, if you need to ignore a top-level attribute named "timestamp" use:

您可以为此使用自定义。例如,如果您需要忽略名为“timestamp”的顶级属性,请使用:

JSONAssert.assertEquals(expectedResponseBody, responseBody,
            new CustomComparator(JSONCompareMode.LENIENT,
                new Customization("timestamp", (o1, o2) -> true)));

It's also possible to use path expressions like "entry.id". In your Customization you can use whatever method you like to compare the two values. The example above always returns true, no matter what the expected value and the actual value are. You could do more complicated stuff there if you need to.

也可以使用像“entry.id”这样的路径表达式。在您的自定义中,您可以使用任何您喜欢的方法来比较这两个值。上面的例子总是返回 true,不管期望值和实际值是什么。如果需要,您可以在那里做更复杂的事情。

It is perfectly fine to ignore that values of multiple attributes, for example:

忽略多个属性的值是完全可以的,例如:

@Test
public void ignoringMultipleAttributesWorks() throws JSONException {
    String expected = "{\"timestamp\":1234567, \"a\":5, \"b\":3 }";
    String actual = "{\"timestamp\":987654, \"a\":1, \"b\":3 }";

    JSONAssert.assertEquals(expected, actual,
            new CustomComparator(JSONCompareMode.LENIENT,
                    new Customization("timestamp", (o1, o2) -> true),
                    new Customization("a", (o1, o2) -> true)
            ));
}

There is one caveat when using Customizations: The attribute whose value is to be compared in a custom way has to be present in the actual JSON. If you want the comparison to succeed even if the attribute is not present at all you would have to override CustomComparator for example like this:

使用自定义时有一个警告:要以自定义方式比较其值的属性必须存在于实际的 JSON 中。如果您希望比较成功,即使该属性根本不存在,您也必须覆盖 CustomComparator,例如:

@Test
public void extendingCustomComparatorToAllowToCompletelyIgnoreCertainAttributes() throws JSONException {
    // AttributeIgnoringComparator completely ignores some of the expected attributes
    class AttributeIgnoringComparator extends CustomComparator{
        private final Set<String> attributesToIgnore;

        private AttributeIgnoringComparator(JSONCompareMode mode, Set<String> attributesToIgnore, Customization... customizations) {
            super(mode, customizations);
            this.attributesToIgnore = attributesToIgnore;
        }

        protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
            Set<String> expectedKeys = getKeys(expected);
            expectedKeys.removeAll(attributesToIgnore);
            for (String key : expectedKeys) {
                Object expectedValue = expected.get(key);
                if (actual.has(key)) {
                    Object actualValue = actual.get(key);
                    compareValues(qualify(prefix, key), expectedValue, actualValue, result);
                } else {
                    result.missing(prefix, key);
                }
            }
        }
    }

    String expected = "{\"timestamp\":1234567, \"a\":5}";
    String actual = "{\"a\":5}";

    JSONAssert.assertEquals(expected, actual,
            new AttributeIgnoringComparator(JSONCompareMode.LENIENT,
                    new HashSet<>(Arrays.asList("timestamp")))
            );
} 

(With this approach you still could use Customizations to compare other attributes' values in the way you want.)

(使用这种方法,您仍然可以使用自定义以您想要的方式比较其他属性的值。)

回答by Srikar

you can use JsonUnitIt has the functionality that you are looking for we can ignore fields, paths, and values that are null etc. Check it out for more info. As for the example, you can ignore a path like this

您可以使用JsonUnit它具有您正在寻找的功能,我们可以忽略字段、路径和空值等。查看更多信息。对于示例,您可以忽略这样的路径

assertJsonEquals(
     "{\"root\":{\"test\":1, \"ignored\": 2}}", 
     "{\"root\":{\"test\":1, \"ignored\": 1}}", 
     whenIgnoringPaths("root.ignored")
);

Sometimes you need to ignore certain values when comparing. It is possible to use ${json-unit.ignore} placeholder like this

有时您需要在比较时忽略某些值。可以像这样使用 ${json-unit.ignore} 占位符

assertJsonEquals("{\"test\":\"${json-unit.ignore}\"}",
    "{\n\"test\": {\"object\" : {\"another\" : 1}}}");

回答by Grigory Kislin

First of all there is open issuefor it.

首先,它有一个未解决的问题

In my tests I compare json from controller with actual object with help of JsonUtilclass for serialization/deserialization:

在我的测试中,我JsonUtil在序列化/反序列化类的帮助下将控制器中的 json 与实际对象进行了比较:

public class JsonUtil {
    public static <T> List<T> readValues(String json, Class<T> clazz) {
        ObjectReader reader = getMapper().readerFor(clazz);
        try {
            return reader.<T>readValues(json).readAll();
        } catch (IOException e) {
            throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e);
        }
    }

    public static <T> T readValue(String json, Class<T> clazz) {
        try {
            return getMapper().readValue(json, clazz);
        } catch (IOException e) {
            throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e);
        }
    }

    public static <T> String writeValue(T obj) {
        try {
            return getMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
        }
    }

To ignore specific object field I've add new method:

为了忽略特定的对象字段,我添加了新方法:

public static <T> String writeIgnoreProps(T obj, String... ignoreProps) {
    try {
        Map<String, Object> map = getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
        for (String prop : ignoreProps) {
            map.remove(prop);
        }
        return getMapper().writeValueAsString(map);
    } catch (JsonProcessingException e) {
        throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
    }
}

and my assert in test now look like this:

我在测试中的断言现在看起来像这样:

            mockMvc.perform(get(REST_URL))
            .andExpect(status().isOk())
            .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
            .andExpect(content().json(JsonUtil.writeIgnoreProps(USER, "registered")))

回答by bhups

Thank you @dknaus for the detailed answer. Although this solution will not work in STRICT mode and checkJsonObjectKeysExpectedInActualmethod code needs to be replaced by following code [As suggested by @tk-gospodinov]:

谢谢@dknaus 的详细回答。尽管此解决方案在 STRICT 模式下不起作用,并且checkJsonObjectKeysExpectedInActual需要将方法代码替换为以下代码 [如@tk-gospodinov 所建议的那样]:

 for (String attribute : attributesToIgnore) {
     expected.remove(attribute);
     super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
  }