1. 概述
JSON 本质上是数据的字符串表示形式。在实际开发中,我们经常需要在算法或单元测试中对 JSON 数据进行比较。虽然可以直接比较包含 JSON 的字符串,但字符串比较对格式敏感——哪怕只是多了一个空格或键的顺序不同,也会导致比较失败,即使语义上它们是完全相同的。
要实现语义层面的 JSON 比较,我们需要将 JSON 字符串解析成内存中的结构化对象,这样就能忽略空白字符、键的顺序等无关紧要的差异。
本文将介绍如何使用 Gson —— Google 提供的 JSON 序列化/反序列化库,来实现 JSON 对象的深度比较。✅
2. 不同字符串表示的语义相同 JSON
先来看一个典型的“踩坑”场景。
假设我们有两个 JSON 字符串,内容语义完全一样,但格式略有不同:
String string1 = "{\"fullName\": \"Emily Jenkins\", \"age\": 27 }";
String string2 = "{\"fullName\": \"Emily Jenkins\", \"age\": 27}";
虽然这两个 JSON 表达的是同一个对象,但直接字符串比较会失败:
assertNotEquals(string1, string2);
同样,如果对象中 key 的顺序不同,字符串比较也会失败,尽管 JSON 标准本身并不关心 key 的顺序:
String string1 = "{\"fullName\": \"Emily Jenkins\", \"age\": 27}";
String string2 = "{\"age\": 27, \"fullName\": \"Emily Jenkins\"}";
assertNotEquals(string1, string2);
⚠️ 所以,直接用 String.equals()
比较 JSON 是非常不可靠的。我们应该使用专门的 JSON 处理库来进行语义比较。
3. Maven 依赖
使用 Gson 前,先引入 Maven 依赖:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
这个库轻量、稳定,是 Java 生态中最主流的 JSON 工具之一,几乎零学习成本。
4. 将 JSON 解析为 Gson 对象
在比较之前,得先了解 Gson 是如何在 Java 中表示 JSON 的。
Gson 提供了 JsonParser
类,可以将 JSON 字符串解析为一个 JsonElement
树结构:
String objectString = "{\"customer\": {\"fullName\": \"Emily Jenkins\", \"age\": 27 }}";
String arrayString = "[10, 20, 30]";
JsonElement json1 = JsonParser.parseString(objectString);
JsonElement json2 = JsonParser.parseString(arrayString);
JsonElement
是一个抽象类,代表任意 JSON 元素。它的具体实现有四种:
JsonObject
:对应 JSON 对象JsonArray
:对应 JSON 数组JsonPrimitive
:对应字符串、数字、布尔值等基本类型JsonNull
:对应null
可以通过类型判断确认:
assertTrue(json1.isJsonObject());
assertTrue(json2.isJsonArray());
关键来了:这些子类都重写了 Object.equals()
方法,实现了深度、语义化的比较逻辑。这才是我们能安全比较 JSON 的核心。✅
5. Gson 比较的典型使用场景
5.1. 比较两个简单 JSON 对象
假设我们有两个 JSON 字符串,内容相同但 key 顺序不同。
string1
中 fullName
在前:
{
"customer": {
"id": 44521,
"fullName": "Emily Jenkins",
"age": 27
}
}
string2
中 age
在前:
{
"customer": {
"id": 44521,
"age": 27,
"fullName": "Emily Jenkins"
}
}
使用 Gson 解析后直接比较:
JsonParser parser = new JsonParser();
assertEquals(parser.parse(string1), parser.parse(string2));
✅ 测试通过!因为 JsonObject.equals()
不关心 key 的顺序,这才是我们想要的语义比较。
5.2. 比较两个 JSON 数组
对于数组,JsonParser
返回的是 JsonArray
。
比如 string1
是:
[10, 20, 30]
验证类型:
assertTrue(JsonParser.parseString(string1).isJsonArray());
再看 string2
,顺序不同:
[20, 10, 30]
比较:
assertNotEquals(JsonParser.parseString(string1), JsonParser.parseString(string2));
✅ 测试通过(即两者不相等)!因为 JsonArray.equals()
是顺序敏感的,这符合数组的语义——顺序本身就是数据的一部分。
⚠️ 注意:如果你希望忽略顺序比较数组,需要先排序或转为集合再比较,Gson 默认不支持无序数组比较。
5.3. 比较两个嵌套 JSON 对象
Gson 的 equals
支持递归深度比较,嵌套结构也没问题。
string1
如下:
{
"customer": {
"id": "44521",
"fullName": "Emily Jenkins",
"age": 27,
"consumption_info": {
"fav_product": "Coke",
"last_buy": "2012-04-23"
}
}
}
string2
中嵌套对象的 key 顺序不同:
{
"customer": {
"fullName": "Emily Jenkins",
"id": "44521",
"age": 27,
"consumption_info": {
"last_buy": "2012-04-23",
"fav_product": "Coke"
}
}
}
直接比较:
assertEquals(JsonParser.parseString(string1), JsonParser.parseString(string2));
✅ 测试通过!Gson 会递归比较每一层的 JsonObject
和 JsonArray
,完全忽略格式和顺序差异。
6. 总结
- ❌ 不要用
String.equals()
比较 JSON,容易踩坑。 - ✅ 使用 Gson 的
JsonParser.parseString()
将 JSON 转为JsonElement
,再用equals()
进行语义比较。 JsonObject.equals()
忽略 key 顺序,适合对象比较。JsonArray.equals()
顺序敏感,符合数组语义。- 嵌套结构自动递归比较,简单粗暴有效。
所有示例代码已上传至 GitHub:https://github.com/yourname/json-examples(mock 地址)