当前位置: 首页 > news >正文

Json结构解析比较

文章目录

  • 前言
  • 正文
    • 一、项目简介
    • 二、核心代码
      • 1、 JavaBeanParser
      • 2、 JsonStructCompare
      • 3、 Client
    • 测试结果

前言

本次练习,主要是针对于两个Json的结构差异。
多用于测试场景,比如一个很大的Json报文,需要和现有的Json报文对比,看看哪些字段没传递。亦或是新旧应用交替,使用Java应用代替其他应用,对比原先和现在的报文结构等。

关键改动在于:

  • 实现了通过javaBean的Class,解析获取一个包含所有字段的完整Json结构。
  • 实现了两个Json的比较,并记录差异节点路径;输出比较的日志。

如果需要严格对比报文的值,则可以参考这篇文章:https://blog.csdn.net/FBB360JAVA/article/details/129259324

正文

一、项目简介

本次使用了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:用于测试以上的俩文件。提供使用示例。

1、 JavaBeanParser

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*/@SneakyThrowspublic 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;}// 当前是一个普通的beanif (!Collection.class.isAssignableFrom(type)) {// 对象数据类型Object fieldObject = type.getDeclaredConstructor().newInstance();parseJavaBean(fieldObject);declaredField.setAccessible(true);declaredField.set(javaBean, fieldObject);continue;}// 集合类型Type fieldType = declaredField.getGenericType();// 检查类型是否为 ParameterizedTypeif (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);}}}
}

2、 JsonStructCompare

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 比较结果,如果存在差异,则返回差异的节点路径*/@SneakyThrowspublic List<String> compare(String itemJson) {ObjectMapper objectMapper = new ObjectMapper();List<String> result = new ArrayList<>();// 读取完整结构的jsonJsonNode fullStructJsonNode = objectMapper.readTree(fullStructJson);Map<String, Object> resultMap1 = new LinkedHashMap<>(16);traverseJsonTreeLeafNode(resultMap1, "", fullStructJsonNode);// 读取目标jsonJsonNode itemJsonNode = objectMapper.readTree(itemJson);Map<String, Object> resultMap2 = new LinkedHashMap<>(16);traverseJsonTreeLeafNode(resultMap2, "", itemJsonNode);// 比较完整结构的json和目标json,获取目标json中不存在的keyresultMap1.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);}}}
}

3、 Client

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);}@Datapublic 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}@Datapublic static class Hobby {private String name;private String type;}@Datapublic 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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 69、ncnn学习onnx2ncnn不支持带三维算子相乘gemm/repeat转换方法学习
  • CH04_依赖项属性
  • day02 mybatis
  • 微信小程序数组绑定使用案例(二)
  • 《流程引擎原理与实践》开源电子书
  • 【大数据专题】数据仓库
  • 从零开始手写STL库:List
  • Huawei、Cisco 路由中 RIP 协议 summary 的用法
  • 基于深度学习的商品推荐
  • C语言航空售票系统
  • HackTheBox--Knife
  • golang 基础 泛型编程
  • DB-GPT:LLM应用的集大成者
  • 【关于PHP性能优化,内存优化,日志工具等问题处理】
  • Python面试整理-Python中的控制流语句
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • JavaScript 奇技淫巧
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Mithril.js 入门介绍
  • Object.assign方法不能实现深复制
  • Python进阶细节
  • React-生命周期杂记
  • Vue官网教程学习过程中值得记录的一些事情
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 关于Flux,Vuex,Redux的思考
  • 深度学习中的信息论知识详解
  • 线上 python http server profile 实践
  • 硬币翻转问题,区间操作
  • 用 Swift 编写面向协议的视图
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 2017年360最后一道编程题
  • zabbix3.2监控linux磁盘IO
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ######## golang各章节终篇索引 ########
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #NOIP 2014#Day.2 T3 解方程
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • ${factoryList }后面有空格不影响
  • $GOPATH/go.mod exists but should not goland
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (八)Flask之app.route装饰器函数的参数
  • (补充)IDEA项目结构
  • (二)WCF的Binding模型
  • (分布式缓存)Redis哨兵
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (一)80c52学习之旅-起始篇
  • (转)ABI是什么
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • **PHP分步表单提交思路(分页表单提交)
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .bat批处理(四):路径相关%cd%和%~dp0的区别