赞
踩
本次练习,主要是针对于两个Json的结构差异。
多用于测试场景,比如一个很大的Json报文,需要和现有的Json报文对比,看看哪些字段没传递。亦或是新旧应用交替,使用Java应用代替其他应用,对比原先和现在的报文结构等。
关键改动在于:
如果需要严格对比报文的值,则可以参考这篇文章:https://blog.csdn.net/FBB360JAVA/article/details/129259324
关于实体转JSON,idea中可以使用插件
POJO to JSON
。
安装插件后,在实体类上右键,选择复制JSON,就能粘贴到json结构了。如下图:
本次使用了maven项目,需要引入以下依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.4</version>
</dependency>
测试用的例子是 一个部门中有多个用户,用户本身的属性(多个爱好、性别枚举、生日日期、入职日期)
此次的Json结构解析,一共涉及3个文件。
JavaBeanParser
:对javaBean进行解析,使用java反射,解析一个Class的变量。提供构造器和解析获取一个javaBean对应的完整字段的Json。特别注意,会解析集合以及集合的范型,使用反射创建一个空对象并存到集合。过滤条件中,会过滤常见的基本数据类型和包装类型,数字、日期、枚举、数组(数组不做处理,一般情况下建议使用集合代替数组)也做了过滤。Map类型也不做处理。JsonStructCompare
:比较Json结构,提供构造器传入一个JavaBean的Class或一个模版Json,比较方法的入参传入要比较的Json,最终会返回比较结果。特别注意,仅比较结构。Client
:用于测试以上的俩文件。提供使用示例。package org.song.json; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; /** * JavaBean解析器类,用于解析JavaBean对象并获取其属性信息。 */ @Slf4j public class JavaBeanParser { private final Class<?> rootClass; private static final Set<Class<?>> SKIP_CLASS_SET = new HashSet<>(); private static final Set<Class<?>> SKIP_ASSIGNABLE_FROM_SET = new HashSet<>(); static { SKIP_CLASS_SET.add(Long.class); SKIP_CLASS_SET.add(long.class); SKIP_CLASS_SET.add(Integer.class); SKIP_CLASS_SET.add(int.class); SKIP_CLASS_SET.add(String.class); SKIP_CLASS_SET.add(BigDecimal.class); SKIP_CLASS_SET.add(Double.class); SKIP_CLASS_SET.add(double.class); SKIP_CLASS_SET.add(Float.class); SKIP_CLASS_SET.add(float.class); SKIP_CLASS_SET.add(Date.class); SKIP_CLASS_SET.add(LocalDate.class); SKIP_CLASS_SET.add(LocalDateTime.class); SKIP_CLASS_SET.add(Boolean.class); SKIP_CLASS_SET.add(boolean.class); SKIP_ASSIGNABLE_FROM_SET.add(Enum.class); SKIP_ASSIGNABLE_FROM_SET.add(Character.class); SKIP_ASSIGNABLE_FROM_SET.add(Map.class); } public JavaBeanParser(Class<?> rootClass) { this.rootClass = rootClass; } /** * 将javaBean转换为json * * @return json */ @SneakyThrows public String parseToJson() { ObjectMapper objectMapper = new ObjectMapper(); Object javaBean = rootClass.getDeclaredConstructor().newInstance(); parseJavaBean(javaBean); return objectMapper.writeValueAsString(javaBean); } private void parseJavaBean(Object javaBean) throws Exception { Field[] declaredFields = javaBean.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { Class<?> type = declaredField.getType(); if (SKIP_CLASS_SET.contains(type)) { continue; } if (SKIP_ASSIGNABLE_FROM_SET.stream().anyMatch(t -> t.isAssignableFrom(type))) { continue; } // 不处理数组,通常javaBean中使用集合 if (type.isArray()) { continue; } // 当前是一个普通的bean if (!Collection.class.isAssignableFrom(type)) { // 对象数据类型 Object fieldObject = type.getDeclaredConstructor().newInstance(); parseJavaBean(fieldObject); declaredField.setAccessible(true); declaredField.set(javaBean, fieldObject); continue; } // 集合类型 Type fieldType = declaredField.getGenericType(); // 检查类型是否为 ParameterizedType if (fieldType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) fieldType; // 获取实际类型参数,第一个参数通常是 List 的泛型类型 Type actualType = parameterizedType.getActualTypeArguments()[0]; String typeName = actualType.getTypeName(); log.info("存在集合{}<{}> {}", type.getName(), typeName, declaredField.getName()); List<Object> list = new ArrayList<>(); Class<?> aClass = Class.forName(typeName); Object fieldBean = aClass.getDeclaredConstructor().newInstance(); parseJavaBean(fieldBean); list.add(fieldBean); declaredField.setAccessible(true); declaredField.set(javaBean, list); } } } }
package org.song.json; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.SneakyThrows; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * JsonStructCompare类用于比较两个JSON结构是否相同。 * 该类提供了方法来初始化比较的JSON字符串,并执行比较操作。 */ @Getter public class JsonStructCompare { private final String fullStructJson; public JsonStructCompare(String fullStructJson) { this.fullStructJson = fullStructJson; } public JsonStructCompare(Class<?> javaBeanClass) { JavaBeanParser javaBeanParser = new JavaBeanParser(javaBeanClass); this.fullStructJson = javaBeanParser.parseToJson(); } /** * 比较完整结构的json和目标json,获取itemJson中不存在的key * * @param itemJson 目标json * @return 比较结果,如果存在差异,则返回差异的节点路径 */ @SneakyThrows public List<String> compare(String itemJson) { ObjectMapper objectMapper = new ObjectMapper(); List<String> result = new ArrayList<>(); // 读取完整结构的json JsonNode fullStructJsonNode = objectMapper.readTree(fullStructJson); Map<String, Object> resultMap1 = new LinkedHashMap<>(16); traverseJsonTreeLeafNode(resultMap1, "", fullStructJsonNode); // 读取目标json JsonNode itemJsonNode = objectMapper.readTree(itemJson); Map<String, Object> resultMap2 = new LinkedHashMap<>(16); traverseJsonTreeLeafNode(resultMap2, "", itemJsonNode); // 比较完整结构的json和目标json,获取目标json中不存在的key resultMap1.forEach((key, value) -> { if (!resultMap2.containsKey(key)) { result.add(key); } }); return result; } /** * 遍历Json树形节点,获取其叶子节点的路径 * * @param map 结过存储(key是节点路径,value是节点对应的值) * @param path 节点路径 * @param jsonNode json节点,对应的是一个json串 */ private void traverseJsonTreeLeafNode(Map<String, Object> map, String path, JsonNode jsonNode) { // 值节点 if (jsonNode.isValueNode()) { map.put(path, String.valueOf(jsonNode)); return; } // 对象节点 if (jsonNode.isObject()) { jsonNode.fields().forEachRemaining(entry -> { traverseJsonTreeLeafNode(map, path + "/" + entry.getKey(), entry.getValue()); }); } // 数组节点 if (jsonNode.isArray()) { int i = 0; for (JsonNode node : jsonNode) { // 这里只比较json结构,因此i=0即可。如果需要比较数组中的多个元素的内容,这里需要对i进行自增 traverseJsonTreeLeafNode(map, path + "[" + i + "]", node); } } } }
package org.song.json; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.util.Date; import java.util.List; @Slf4j public class Client { public static void main(String[] args) { JavaBeanParser parser = new JavaBeanParser(Department.class); String fullStructJson = parser.parseToJson(); log.info("完整的Json结构为:{}", fullStructJson); JsonStructCompare jsonStructCompare = new JsonStructCompare(fullStructJson); String itemJson = "{\"name\":null,\"users\":[{\"id\":null,\"name\":null,\"birthday\":null,\"age\":0,\"hobbies\":[{\"type\":null}]}]}"; List<String> compareResult = jsonStructCompare.compare(itemJson); log.info("存在差异性的节点路径有{}个,明细如下:", compareResult.size()); compareResult.forEach(log::info); } @Data public static class User { private String id; private String name; private SexEnum sex; private LocalDateTime birthday; private int age; private List<Hobby> hobbies; /** * 入职时间 */ private Date entryTime; } public enum SexEnum { MALE, FEMALE } @Data public static class Hobby { private String name; private String type; } @Data public static class Department { private String name; private List<User> users; } }
17:22:32.768 [main] INFO org.song.json.JavaBeanParser - 存在集合java.util.List<org.song.json.Client$User> users
17:22:32.772 [main] INFO org.song.json.JavaBeanParser - 存在集合java.util.List<org.song.json.Client$Hobby> hobbies
17:22:32.848 [main] INFO org.song.json.Client - 完整的Json结构为:{"name":null,"users":[{"id":null,"name":null,"sex":null,"birthday":null,"age":0,"hobbies":[{"name":null,"type":null}],"entryTime":null}]}
17:22:32.871 [main] INFO org.song.json.Client - 存在差异性的节点路径有3个,明细如下:
17:22:32.872 [main] INFO org.song.json.Client - /users[0]/sex
17:22:32.872 [main] INFO org.song.json.Client - /users[0]/hobbies[0]/name
17:22:32.872 [main] INFO org.song.json.Client - /users[0]/entryTime
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。