游乐游手机版
首页/编程语言/文章详情

Java中JSONassert、JsonUnit与Jackson对比

时间:2026-07-23 19:07
断言工具专为测试场景设计,支持宽松模式并精准定位差异;单元工具基于断言工具和杰克逊库封装,提供更友好的断言接口;杰克逊库作为主流处理库,可通过节点树模型自定义比对逻辑,适用于业务开发中精细控制差异的场景。

在日常开发工作中,我们经常需要比对两组JSON数据,并快速返回差异结果。经过调研,我发现了几款优秀的JSON比对工具,这里整理给大家,按场景选型即可。

核心工具对比

Ja va中JSONassert,JsonUnit和Jackson三方比对

1. JSONassert(测试场景首选)

JSONassert 这工具,从一开始就是为测试场景量身打造的。它支持忽略字段顺序、数组顺序、数值精度等宽松模式,遇到差异直接抛出异常,而且异常信息中会清晰定位到是哪个路径下的哪个值出了问题。

步骤 1:引入依赖(Maven)


    org.skyscreamer
    jsonassert
    1.5.1
    test 

步骤 2:实操示例(比对差异并输出)

import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;

public class JsonAssertDemo {
    public static void main(String[] args) {
        // 待比对的两个JSON字符串(结构相同,值有差异)
        String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"},"hobbies":["篮球","游泳"]}";
        String actualJson = "{"name":"李四","age":20,"address":{"city":"上海"},"hobbies":["篮球","跑步"]}";

        try {
            // 严格模式(字段顺序、数组顺序、值完全一致才通过)
            JSONAssert.assertEquals(expectedJson, actualJson, JSONCompareMode.STRICT);
        } catch (AssertionError e) {
            // 捕获差异并输出
            System.out.println("JSON差异:n" + e.getMessage());
        }
    }
}

输出结果(精准定位差异)

JSON差异:

Expected: "张三"

got: "李四"

at path $["name"]

Expected: "北京"

got: "上海"

at path $["address"]["city"]

Expected: "游泳"

got: "跑步"

at path $["hobbies"][1]

关键配置(JSONCompareMode)

  • STRICT:严格模式(字段顺序、数组顺序、值必须完全一致)
  • LENIENT:宽松模式(忽略字段顺序,数组顺序仍校验)
  • NON_EXTENSIBLE:不允许实际 JSON 包含预期外的字段
  • IGNORE_ARRAY_ORDER:忽略数组元素顺序
  • IGNORE_VALUES:只校验结构,忽略值的差异

2. JsonUnit(更友好的测试 API)

JsonUnit 是在 JSONassert 和 Jackson 基础上封装的一层,API 设计得更加简洁,差异输出也更易读。它支持 assertThat 风格的断言,写起测试来很顺手,适合 BDD 测试风格。

步骤 1:引入依赖


    net.javacrumbs.json-unit
    json-unit-core
    2.38.0
    test



    com.fasterxml.jackson.core
    jackson-databind
    2.15.2

步骤 2:实操示例

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;

public class JsonUnitDemo {
    public static void main(String[] args) {
        String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"}}";
        String actualJson = "{"name":"李四","age":21,"address":{"city":"北京"}}";

        // 比对并输出差异(AssertJ风格)
        assertThatJson(actualJson)
            .isEqualTo(expectedJson)
            .onFailure(failure -> System.out.println("JSON差异:n" + failure.getMessage()));
    }
}

输出结果

JSON差异:

Expected value <"张三"> but was <"李四"> at path $['name']

Expected value <20> but was <21> at path $['age']

高级用法(忽略指定字段)

// 忽略age字段的差异
assertThatJson(actualJson)
    .whenIgnoringPaths("age")
    .isEqualTo(expectedJson);

3. Jackson(业务开发灵活定制)

Jackson 是 Java 生态里最主流的 JSON 处理库,没有之一。通过它的 JsonNode 树模型,你可以完全自定义比对逻辑,这在业务开发中需要精细控制差异的场景下特别有用。

步骤 1:引入依赖


    com.fasterxml.jackson.core
    jackson-databind
    2.15.2

步骤 2:自定义比对工具类(输出差异路径和值)

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
public class JacksonJsonDiff {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final List DIFFS = new ArrayList<>();
    // 比对两个JSON字符串,返回差异列表
    public static List compareJson(String expectedJson, String actualJson) throws Exception {
        DIFFS.clear();
        JsonNode expectedNode = OBJECT_MAPPER.readTree(expectedJson);
        JsonNode actualNode = OBJECT_MAPPER.readTree(actualJson);
        compareNodes("", expectedNode, actualNode);
        return DIFFS;
    }
    // 递归比对JsonNode节点
    private static void compareNodes(String path, JsonNode expected, JsonNode actual) {
        // 节点类型不同
        if (expected.getNodeType() != actual.getNodeType()) {
            DIFFS.add(path + ":类型不一致(预期:" + expected.getNodeType() + ",实际:" + actual.getNodeType() + ")");
            return;
        }
        // 叶子节点(值节点)
        if (expected.isValueNode()) {
            if (!expected.equals(actual)) {
                DIFFS.add(path + ":值不一致(预期:" + expected.asText() + ",实际:" + actual.asText() + ")");
            }
            return;
        }
        // 对象节点(递归比对子字段)
        if (expected.isObject()) {
            expected.fieldNames().forEachRemaining(fieldName -> {
                String newPath = path.isEmpty() ? "$." + fieldName : path + "." + fieldName;
                JsonNode expectedChild = expected.get(fieldName);
                JsonNode actualChild = actual.get(fieldName);
                if (actualChild == null) {
                    DIFFS.add(newPath + ":实际JSON缺失该字段");
                } else {
                    compareNodes(newPath, expectedChild, actualChild);
                }
            });
            // 检查实际JSON是否有预期外的字段
            actual.fieldNames().forEachRemaining(fieldName -> {
                if (!expected.has(fieldName)) {
                    String newPath = path.isEmpty() ? "$." + fieldName : path + "." + fieldName;
                    DIFFS.add(newPath + ":实际JSON包含预期外的字段");
                }
            });
        }
        // 数组节点(递归比对数组元素)
        if (expected.isArray()) {
            int expectedSize = expected.size();
            int actualSize = actual.size();
            if (expectedSize != actualSize) {
                DIFFS.add(path + ":数组长度不一致(预期:" + expectedSize + ",实际:" + actualSize + ")");
            }
            // 比对数组元素(按索引)
            int maxSize = Math.max(expectedSize, actualSize);
            for (int i = 0; i < maxSize; i++) {
                String newPath = path + "[" + i + "]";
                JsonNode expectedChild = i < expectedSize ? expected.get(i) : null;
                JsonNode actualChild = i < actualSize ? actual.get(i) : null;
                if (expectedChild == null) {
                    DIFFS.add(newPath + ":实际数组多出元素");
                } else if (actualChild == null) {
                    DIFFS.add(newPath + ":实际数组缺失元素");
                } else {
                    compareNodes(newPath, expectedChild, actualChild);
                }
            }
        }
    }
    // 测试
    public static void main(String[] args) throws Exception {
        String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"},"hobbies":["篮球","游泳"]}";
        String actualJson = "{"name":"李四","age":20,"address":{"city":"上海"},"hobbies":["篮球","跑步"]}";
        List diffs = compareJson(expectedJson, actualJson);
        System.out.println("JSON差异列表:");
        diffs.forEach(diff -> System.out.println("- " + diff));
    }
}

输出结果

JSON差异列表:

- $.name:值不一致(预期:张三,实际:李四)

- $.address.city:值不一致(预期:北京,实际:上海)

- $.hobbies[1]:值不一致(预期:游泳,实际:跑步)

4. Diffuse(轻量级结构化差异输出)

Diffuse 是一个轻量级工具,它的特色是能输出 JSON 格式的结构化差异报告,把添加、修改、删除的节点都给你列出来。如果你需要把差异持久化或者传输给其他系统,这个工具就很合适。

步骤 1:引入依赖


    me.snov
    diffuse
    0.2.0

步骤 2:实操示例

import me.snov.diffuse.Diffuse;
import me.snov.diffuse.JsonDiff;

public class DiffuseDemo {
    public static void main(String[] args) {
        String expectedJson = "{"name":"张三","age":20}";
        String actualJson = "{"name":"李四","age":21,"gender":"男"}";

        // 生成结构化差异报告
        JsonDiff diff = Diffuse.diff(expectedJson, actualJson);
        System.out.println("结构化差异报告:n" + diff.toJson());
    }
}

输出结果(JSON 格式)

{
  "changed": [
    {
      "path": "/name",
      "from": "张三",
      "to": "李四"
    },
    {
      "path": "/age",
      "from": 20,
      "to": 21
    }
  ],
  "added": [
    {
      "path": "/gender",
      "value": "男"
    }
  ],
  "removed": []
}

总结一下,选型并不复杂:

  1. 测试场景:优先选 JsonUnit(API 友好)或 JSONassert(轻量级);
  2. 业务开发:优先选 Jackson(灵活自定义比对规则);
  3. 需要结构化差异输出:选 Diffuse;
  4. 特殊需求(如忽略数值精度、时间格式):基于 Jackson 扩展自定义比对逻辑。
来源:https://www.jb51.net/program/367941bfs.htm
上一篇C++中哈希映射与哈希集合的高效使用详解指南 下一篇Python PDF拆分实战:单页与按需页码范围拆分详解
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
FileZilla断点续传设置与操作指南
编程语言 · 2026-07-25

FileZilla断点续传设置与操作指南

FileZilla支持断点续传,需客户端与服务器均开启REST命令。设置中确保启用断点续传及继续传输选项。中断后自动或手动从断点恢复。注意服务器支持、传输模式匹配及文件完整性校验。

Debian系统C++编译器位置查找方法
编程语言 · 2026-07-25

Debian系统C++编译器位置查找方法

在Debian系统中,通过apt安装的C++编译器g++默认位于 usr bin g++,可使用which或whereis命令验证路径。g++属于build-essential软件包,若未安装则需执行sudoaptinstallbuild-essential。该包还包含gcc、make等编译工具链,g++是GNUC++编译器,实际是符号链接指向具体版本,验证

Debian系统安装C++环境的方法
编程语言 · 2026-07-25

Debian系统安装C++环境的方法

在Debian系统安装C++开发环境:先sudoaptupdate更新包列表,再sudoaptinstallbuild-essential安装编译工具链,或单独安装g++。用g++--version验证。可选安装VSCode、GDB、CMake等工具并配置默认编译器版本。

Debian系统C++开发环境配置指南
编程语言 · 2026-07-25

Debian系统C++开发环境配置指南

在Debian系统中,先执行aptupdate更新软件包列表,再安装build-essential元包即可获得GCC、G++、Make和GDB。通过运行g++--version命令验证编译器安装成功。可选安装VisualStudioCode、CLion等编辑器及CMake构建工具,并编写一个简单的HelloWorld程序,使用g++编译运行以验证环境配置正确

通过cpustat工具查看CPU状态的具体方法与详细步骤
编程语言 · 2026-07-25

通过cpustat工具查看CPU状态的具体方法与详细步骤

cpustat是sysstat包中的CPU监控工具,可按固定间隔输出带时间戳的CPU使用率统计。安装后运行cpustat即可实时显示各核心信息,常用指标包括%usr、%sys、%iowait、%steal和%idle,用于定位用户态、内核态或I O瓶颈。高级选项-c可显示单核统计,-m可同时查看内存使用,适合脚本采集和性能分析。