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 顺序不同。

string1fullName 在前:

{
    "customer": {
        "id": 44521,
        "fullName": "Emily Jenkins",
        "age": 27
    }
}

string2age 在前:

{
    "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 会递归比较每一层的 JsonObjectJsonArray,完全忽略格式和顺序差异。

6. 总结

  • ❌ 不要用 String.equals() 比较 JSON,容易踩坑。
  • ✅ 使用 Gson 的 JsonParser.parseString() 将 JSON 转为 JsonElement,再用 equals() 进行语义比较。
  • JsonObject.equals() 忽略 key 顺序,适合对象比较。
  • JsonArray.equals() 顺序敏感,符合数组语义。
  • 嵌套结构自动递归比较,简单粗暴有效。

所有示例代码已上传至 GitHub:https://github.com/yourname/json-examples(mock 地址)


原始标题:Compare Two JSON Objects with Gson