JUnit中比较JSON对象:需无视键值顺序并忽略指定动态字段
解决JUnit中JSON对象比较的问题:忽略动态字段与键值对顺序
我之前也遇到过一模一样的问题!JUnit默认的assertEquals比较的是对象引用,而不是实际的字段内容,所以哪怕两个对象的字段值完全一致,只要是不同的实例就会抛出java.lang.AssertionError。针对你需要忽略动态字段(比如timestamp、id)、不关心JSON键值对顺序的需求,这里有几个靠谱的解决方案:
方案一:利用Gson的JsonElement进行内容比较(无需额外依赖)
Gson的JsonElement类重写了equals方法,会递归比较JSON的内容,而且不关心键的顺序。我们可以通过序列化时排除指定字段,只保留需要校验的部分来实现需求。
步骤1:配置Gson排除动态字段
有两种方式排除字段:
- 方式A:使用@Expose注解:在实体类的需要校验的字段上添加
@Expose,动态字段不加,然后构建Gson时开启排除未标注的字段。 - 方式B:自定义排除策略:直接指定要忽略的字段名,无需修改实体类。
示例代码(方式A):
import com.google.gson.annotations.Expose; public class Data { @Expose // 需要校验的字段 private String name; @Expose private String age; private String timestamp; // 忽略的动态字段 private String id; // 忽略的动态字段 // 生成getter、setter方法 }
测试代码:
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import org.junit.Test; import static org.junit.Assert.assertEquals; public class DataTest { @Test public void testJsonEqualityIgnoringDynamicFields() { String json1 = "{\"name\": \"ray\", \"age\": \"20\", \"timestamp\": \"03:10:54\", \"id\": \"123\"}"; String json2 = "{\"age\": \"20\", \"name\": \"ray\", \"timestamp\": \"04:15:22\", \"id\": \"456\"}"; // 构建Gson实例,排除未标注@Expose的字段 Gson gson = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create(); Data data1 = gson.fromJson(json1, Data.class); Data data2 = gson.fromJson(json2, Data.class); // 把对象转回JsonElement,比较内容 JsonElement jsonElement1 = gson.toJsonTree(data1); JsonElement jsonElement2 = gson.toJsonTree(data2); assertEquals(jsonElement1, jsonElement2); // 断言成功,因为忽略了动态字段且不关心键顺序 } }
示例代码(方式B,无需修改实体类):
如果不想修改实体类,可以用addSerializationExclusionStrategy指定要排除的字段:
Gson gson = new GsonBuilder() .addSerializationExclusionStrategy(new ExclusionStrategy() { @Override public boolean shouldSkipField(FieldAttributes f) { // 返回true表示排除该字段 return "timestamp".equals(f.getName()) || "id".equals(f.getName()); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } }) .create();
方案二:使用AssertJ库(更灵活简洁)
AssertJ是一个增强型的断言库,提供了强大的递归对象比较功能,可以直接指定忽略的字段,写法非常直观,无需修改实体类或Gson配置。
测试代码示例:
import com.google.gson.Gson; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class DataTest { @Test public void testDataEqualityWithAssertJ() { String json1 = "{\"name\": \"ray\", \"age\": \"20\", \"timestamp\": \"03:10:54\", \"id\": \"123\"}"; String json2 = "{\"age\": \"20\", \"name\": \"ray\", \"timestamp\": \"04:15:22\", \"id\": \"456\"}"; Gson gson = new Gson(); Data data1 = gson.fromJson(json1, Data.class); Data data2 = gson.fromJson(json2, Data.class); // 递归比较对象,忽略指定字段 assertThat(data1) .usingRecursiveComparison() .ignoringFields("timestamp", "id") .isEqualTo(data2); // 断言成功 } }
方案三:手动比较字段(适合字段较少的场景)
如果你的实体类字段不多,也可以手动逐个比较需要校验的字段,跳过动态字段:
@Test public void testDataEqualityManually() { String json1 = "{\"name\": \"ray\", \"age\": \"20\", \"timestamp\": \"03:10:54\", \"id\": \"123\"}"; String json2 = "{\"age\": \"20\", \"name\": \"ray\", \"timestamp\": \"04:15:22\", \"id\": \"456\"}"; Gson gson = new Gson(); Data data1 = gson.fromJson(json1, Data.class); Data data2 = gson.fromJson(json2, Data.class); assertEquals(data1.getName(), data2.getName()); assertEquals(data1.getAge(), data2.getAge()); // 不比较timestamp和id字段 }
总结
- 如果你不想引入额外依赖,优先选方案一;
- 如果追求代码简洁灵活,推荐使用方案二(AssertJ在Java测试中非常常用,功能远超JUnit默认断言);
- 字段极少的情况下可以用方案三。
内容的提问来源于stack exchange,提问作者Srikar
相关产品推荐
相关产品推荐

