【问题标题】:Ignore specific nodes/attributes while comparing two JSONs比较两个 JSON 时忽略特定节点/属性
【发布时间】:2016-12-15 16:02:38
【问题描述】:

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

我目前正在使用 org.SkyScreamer 的 JSONAssert 进行比较。它为我提供了不错的控制台输出,但不会忽略任何属性。

例如

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

现在这是动态的,应该被忽略。类似的东西

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

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

【问题讨论】:

  • 如果您提供 JSONCompareMode 为 false 会发生什么?是不是忽略了节点?
  • @rishal-dev-singh 不,它不会忽略特定属性
  • @Prateik 作为替代方案,您可以在比较之前删除属性。作为另一种选择,您可以自己做,例如stackoverflow.com/questions/2253750/…
  • @icyrock.com 两者都可以为我工作,短期解决方案从比较本身或其他方式中删除该字段,基于上述帖子实施某些事情。谢谢。

标签: java json jsonassert


【解决方案1】:

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

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

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

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

@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)
            ));
}

使用自定义时有一个警告:要以自定义方式比较其值的属性必须存在于实际的 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")))
            );
} 

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

【讨论】:

  • 当我尝试使用它时,它只使用内置的 JSONComparator 并忽略 lambda。它尝试调用函数 compareJSON 但它不存在所以它转到 defaultComparator。
  • 自定义路径表达式时的重要说明-匹配自定义的路径为正则表达式。
  • 简单Customization可以忽略多个字段吗?
  • @NisargPatil 是的,忽略多个字段的值是完全可以的。但是你不能只用一个Customization。相反,您将使用多个自定义项。我会更新我的回复以反映这一点
  • 您可以将checkJsonObjectKeysExpectedInActual 方法实现简化为for (String attribute : attributesToIgnore) { expected.remove(attribute); } super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
【解决方案2】:

首先是open issue

在我的测试中,我在 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);
        }
    }

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

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);
    }
}

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-04-03
        • 1970-01-01
        • 1970-01-01
        • 2023-01-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多