Java中忽略指定属性比较JSON数组内JSON对象的实现方案
问题场景
现有如下交易结构JSON示例,数组内包含多个交易对象:
{ "payload": { "header": [ { "messageId": "10a0df00-c55c-4147-a3bf-e10dc9882768", "transactionId": "10000001", "customerId": "customer4148", "customerGroupId": "USA", "transactionStatus": "completed", "creationTimestamp": "2022-06-15 06:36:23.798", "currency": "USD", "offset": 30 }, { "messageId": "9cg8f839-ab1e-467f-b679-a3aa4f390f2a", "transactionId": "10000001", "customerId": "customer4148", "customerGroupId": "USA", "transactionStatus": "completed", "creationTimestamp": "2022-06-15 06:36:23.798", "currency": "USD", "offset": 30 } ] } }
此前使用JsonAssert实现自定义比较逻辑,通过自定义比较器忽略messageId字段,实现代码如下:
JSONAssert.assertEquals(expectedResponseBody, responseBody, new CustomComparator(JSONCompareMode.LENIENT, new Customization("messageId", (o1, o2) -> true)));
目前因JsonAssert依赖存在已知安全漏洞,被CI流水线禁止引入,需要寻找可高效比较JSON数组内多个JSONObject、且比较过程中自动忽略messageId字段的替代实现。
推荐实现方案
优先选择项目已有通用依赖实现,避免引入新的第三方包带来额外安全风险:
方案1:基于Jackson实现(推荐,绝大多数Java项目已内置依赖)
Jackson是Spring生态默认集成的JSON处理包,无公开高危漏洞,实现逻辑为:递归移除两个待比较JSON中所有层级的messageId字段(包含数组内嵌套对象的对应字段),再直接做节点等值比较,支持宽松/严格模式切换。
实现代码示例:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Iterator; import java.util.Map; import java.util.Set; public class JsonCompareUtil { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); // 配置需要忽略比较的字段列表 private static final Set<String> IGNORE_FIELDS = Set.of("messageId"); public static void assertJsonEquals(String expected, String actual) throws Exception { JsonNode expectedNode = OBJECT_MAPPER.readTree(expected); JsonNode actualNode = OBJECT_MAPPER.readTree(actual); // 递归移除所有待忽略字段 removeIgnoreFields(expectedNode); removeIgnoreFields(actualNode); // 执行节点等值比较 if (!expectedNode.equals(actualNode)) { throw new AssertionError(String.format("JSON比较不通过,预期:%s,实际:%s", expectedNode, actualNode)); } } private static void removeIgnoreFields(JsonNode node) { if (node.isObject()) { ObjectNode objectNode = (ObjectNode) node; // 移除当前对象层级的忽略字段 IGNORE_FIELDS.forEach(objectNode::remove); // 递归处理所有子字段 Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields(); while (fields.hasNext()) { removeIgnoreFields(fields.next().getValue()); } } else if (node.isArray()) { // 遍历处理数组内所有元素 for (JsonNode arrayItem : node) { removeIgnoreFields(arrayItem); } } } }
注意:如果业务场景下JSON数组是无序的,可在移除忽略字段后,对数组节点按业务唯一键(比如transactionId)排序后再做等值判断,避免元素顺序不一致导致的误判。
方案2:基于AssertJ递归比较(适合已集成AssertJ做单元测试的项目)
如果项目单元测试已经使用AssertJ,不需要额外引入JSON处理依赖,可先将JSON反序列化为Map结构,再通过AssertJ的递归比较API忽略指定字段:
import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import java.util.Map; public class JsonCompareTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Test void testJsonCompare() throws Exception { Map<String, Object> expected = objectMapper.readValue(expectedResponseBody, new TypeReference<>() {}); Map<String, Object> actual = objectMapper.readValue(responseBody, new TypeReference<>() {}); assertThat(actual) .usingRecursiveComparison() .ignoringFieldsMatchingRegexes(".*messageId") // 忽略所有层级的messageId字段 .ignoringCollectionOrder() // 按需开启:忽略集合元素顺序 .isEqualTo(expected); } }
该方案支持多种宽松模式配置,比如忽略空值、忽略字段大小写等,可根据测试需求灵活调整。
方案优势
- 不需要引入JsonAssert依赖,所有依赖包均为Java项目常规内置依赖,安全漏洞风险极低
- 支持任意层级嵌套的JSON结构,自动处理数组内对象的字段忽略逻辑
- 性能优于JsonAssert,大JSON体比较场景下无明显性能损耗
内容的提问来源于stack exchange,提问作者user13906258
相关产品推荐
相关产品推荐

