You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不抛出AssertionError的情况下获取两个对象的可读差异输出(类似AssertJ)

无需抛出异常的对象对比&可读差异方案

我们在生产环境用AssertJ做对象对比时,确实会遇到要写大量try/catch、还要丢弃堆栈的麻烦,下面几个方案可以解决这个问题:

一、AssertJ自带的无异常方案(最贴合需求)

AssertJ本身提供了SoftAssertions类,专门用来收集断言失败信息而不抛出异常,完美匹配你的场景:

// 初始化软断言实例
SoftAssertions softly = new SoftAssertions();
// 沿用你熟悉的AssertJ对比逻辑
softly.assertThat(valueB)
    .usingComparatorForType(BigInteger::compareTo, BigInteger.class)
    .isEqualToComparingFieldByField(valueA);
// 获取所有失败断言(单次对比只会有一个)
List<Throwable> failures = softly.errors();
if (!failures.isEmpty()) {
    // 直接拿到和示例一致的可读差异信息,无需处理堆栈
    String diffMessage = failures.get(0).getMessage();
    // 记录diffMessage即可
}

这个方案完全沿用原有AssertJ语法,不需要额外依赖,也不用手动写try/catch,直接获取差异文本。

二、标准库实现(手动反射对比)

如果不想依赖AssertJ,可以基于Java反射自己实现字段对比逻辑,虽然不如AssertJ健壮,但胜在无外部依赖:

public static String compareObjects(Object expected, Object actual) {
    if (expected == actual) return null;
    if (expected == null || actual == null) return "Expected [" + expected + "] but was [" + actual + "]";
    
    Class<?> clazz = expected.getClass();
    if (!clazz.equals(actual.getClass())) return "Expected type [" + clazz.getName() + "] but was [" + actual.getClass().getName() + "]";
    
    StringBuilder diff = new StringBuilder("Error during validation between value A and value B:\n");
    List<String> fieldNames = new ArrayList<>();
    List<Object> expectedValues = new ArrayList<>();
    List<Object> actualValues = new ArrayList<>();
    
    // 遍历所有字段(可按需调整是否包含父类字段)
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        try {
            Object expVal = field.get(expected);
            Object actVal = field.get(actual);
            
            // 处理BigInteger自定义比较,可扩展其他类型
            boolean equal = false;
            if (expVal instanceof BigInteger && actVal instanceof BigInteger) {
                equal = ((BigInteger) expVal).compareTo((BigInteger) actVal) == 0;
            } else {
                equal = Objects.equals(expVal, actVal);
            }
            
            if (!equal) {
                fieldNames.add(field.getName());
                expectedValues.add(expVal);
                actualValues.add(actVal);
            }
        } catch (IllegalAccessException e) {
            diff.append("Failed to access field ").append(field.getName()).append(": ").append(e.getMessage()).append("\n");
        }
    }
    
    if (!fieldNames.isEmpty()) {
        diff.append("Expecting values:\n  <").append(expectedValues).append(">\n");
        diff.append("in fields:\n  <").append(fieldNames).append(">\n");
        diff.append("but were:\n  <").append(actualValues).append(">\n");
        diff.append("in <").append(actual).append(">.\n");
        diff.append("Comparison was performed on all fields");
        return diff.toString();
    }
    return null;
}

调用这个方法就能得到类似AssertJ格式的差异文本,缺点是需要自己维护类型比较逻辑,比如集合、嵌套对象等场景要额外扩展。

三、第三方工具类方案(JSON对比)

如果对象结构适合转JSON,可以用JSON对比工具生成差异,比如JSONAssert(需引入依赖):

ObjectMapper mapper = new ObjectMapper();
String expectedJson = mapper.writeValueAsString(valueA);
String actualJson = mapper.writeValueAsString(valueB);

// 对比JSON并获取差异信息
String diff = JSONAssert.compareJSON(expectedJson, actualJson, false).getMessage();

这种方案的差异是JSON格式的,可读性良好,适合复杂嵌套对象场景,但需要额外引入Jackson和JSONAssert依赖。

内容的提问来源于stack exchange,提问作者evanjdooner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 17:17:31