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

时间:2016-08-09 14:30:25

标签: java json jsonassert

我想比较两个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中建议解决方案,那将会很棒。但是也欢迎其他方式。

4 个答案:

答案 0 :(得分:10)

您可以使用自定义。例如,如果您需要忽略名为" timestamp"的顶级属性。使用方法:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cfsuite.orm.manual.relationship.self_join">
    <class entity-name="cemployees" table="Employees">
        <tuplizer class="coldfusion.orm.hibernate.CFCTuplizer" entity-mode="dynamic-map"/>
        <id column="EmployeeID" name="EmployeeID" type="integer">
            <generator class="native"/>
        </id>
        <property column="LastName" name="LastName" type="string"/>
        <property column="FirstName" name="FirstName" type="string"/>
        <property column="Title" name="Title" type="string"/>
        <property column="TitleOfCourtesy" name="TitleOfCourtesy" type="string"/>
        <property column="BirthDate" name="BirthDate" type="date"/>
        <property column="HireDate" name="HireDate" type="date"/>
        <property column="Address" name="Address" type="string"/>
        <property column="City" name="City" type="string"/>
        <property column="Region" name="Region" type="string"/>
        <property column="PostalCode" name="PostalCode" type="string"/>
        <property column="Country" name="Country" type="string"/>
        <property column="HomePhone" name="HomePhone" type="string"/>
        <property column="Extension" name="Extension" type="string"/>
        <property column="Photo" name="Photo" type="binary"/>
        <property column="Notes" name="Notes" type="string"/>
        <many-to-one column="ReportsTo" entity-name="cemployees" name="ReportsToObj"/>
        <property column="PhotoPath" name="PhotoPath" type="string"/>
        <bag cascade="all-delete-orphan" name="ReporteesArr">
            <key column="ReportsTo"/>
            <one-to-many entity-name="cemployees"/>
        </bag>
    </class>
</hibernate-mapping>

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

忽略多个属性的值是完全正确的,例如:

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

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

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

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

答案 1 :(得分:4)

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

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

答案 2 :(得分:1)

首先,它有open issue

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

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 :(得分:0)

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

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