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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48966.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【快速逆向二/无过程/有源码】掌上高考—2024高考志愿填报服务平台

逆向日期&#xff1a;2024.07.21 使用工具&#xff1a;Node.js 加密工具&#xff1a;Crypto-js标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&…

百日筑基第二十八天-23种设计模式-行为型总汇

百日筑基第二十八天-23种设计模式-行为型总汇 文章目录 百日筑基第二十八天-23种设计模式-行为型总汇前言模板方法模式简介模板方式的特点模板方法模式结构类图模板方式模式案例分析模板方法模式应用源码分析模板方法模式的注意事项和细节 迭代器模式迭代器模式结构类图迭代器模…

modbus中3.5字节时间如何计算

示例&#xff1a;波特率是115200bps &#xff08;比特每秒&#xff09; 1、计算每个比特的时间 2、每字节时间 1个字符8数据位1起始位1停止位&#xff0c;则每传输一个字符需要10位 3、3.5个字节的时间

【Linux】进程信号 --- 信号处理

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

睿考网:没有初级能直接考中级经济师吗?

经济师考试分为初级、中级、高级三个类别&#xff0c;属于职业资格证书&#xff0c;考试是由统一组织、统一大纲、统一命题的方式。 没有初级可以直接考中级经济师吗? 可以直接报考&#xff0c;如果考生符合中级经济师报考要求&#xff0c;直接报名参加就可以&#xff0c;具…

通过iframe嵌套的不同域名的页面之间处理cookie存储失败的问题——js技能提升

最近同事在写mvc的后台管理系统&#xff0c;通过iframe实现不同域名的页面的嵌套。 但是有个问题&#xff0c;就是从父页面打开iframe的子页面时&#xff0c;需要登录子页面&#xff0c;此时需要将子页面登录后的token存储到子页面的cookie中&#xff0c;方便子页面的其他接口…

SpringBoot+Session+redis实现分布式登录

SpringBootSessionRedis实现分布式登录功能实现 文章目录 目录 文章目录 前言 一、引库 二、修改配置文件 三、使用 四、解决乱码问题 1.引库 2.配置redis序列化 3.配置Session-Redis序列化 前言 这里简单介绍一下&#xff0c;如果你想多台机器部署你的项目的话&#xff0c;在…

Maven的常用命令(面试篇之Maven)

我在写项目时,使用Maven的插件的命令来进行打包等,却发现报错误了,虽然解决了, 但借此机会来总结一下Maven的常用命令: 这些插件都有着自己的命令,虽然,我们可以简化的使用一些idea中的方便的按键: 但 , 一个程序员的功力深浅就在这些细节末尾处: 在Maven中&#xff0c;插件是…

【MySQL进阶之路 | 高级篇】范式概述与第一范式

1. 范式简介 在关系型数据库中&#xff0c;关于数据表的设计的基本原则&#xff0c;规则就称为范式。可以理解为&#xff0c;一张数据表的设计结果需要满足的某种设计标准的级别。要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的范式。 范式的英文名是Normal …

OpenHarmony 入门——ArkUI 自定义组件之间的状态装饰器小结(一)

文章大纲 引言一、状态管理概述二、基本术语三、状态装饰器总览 引言 前面说了ArkTS 是在TypeScript基础上结合ArkUI框架扩展定制的&#xff0c;状态管理中的各种装饰器就是扩展的功能之一&#xff0c;可以让开发者通过声明式UI快速高效实现组件之间的数据同步&#xff0c;至于…

Leetcode之string

目录 前言1. 字符串相加2. 仅仅反转字母3. 字符串中的第一个唯一字符4. 字符串最后一个单词的长度5. 验证回文串6. 反转字符串Ⅱ7. 反转字符串的单词Ⅲ8. 字符串相乘9. 打印日期 前言 本篇整理了一些关于string类题目的练习, 希望能够学以巩固. 博客主页: 酷酷学!!! 点击关注…

转置卷积方法

一、定义 1、卷积神经网络层通常会减少&#xff08;或保持不变&#xff09;采样输入图像的空间维度&#xff08;高和宽&#xff09;&#xff0c;另一种类型的卷积神经网络层&#xff0c;它可以增加上采样中间层特征图的空间维度&#xff0c; 用于逆转下采样导致的空间尺寸减小…

【BES2500x系列 -- RTX5操作系统】系统启动流程 -- boot loader概念讲解 --(九)

&#x1f48c; 所属专栏&#xff1a;【BES2500x系列】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f49…

.NET 情报 | 分析某云系统添加管理员漏洞

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

【计算机毕业设计】881音乐网站

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

跟代码执行流程,读Megatron源码(二)训练入口pretrain_gpt.py

Megatron-LM默认支持GPT、T5、BERT等多个常见模型的预训练&#xff0c;当下大模型流行&#xff0c;故以pretrain_gpt.py为例做源码的走读。 一. 启动pretrain_gpt.py pretrain_gpt.py为GPT类模型的训练入口&#xff0c;它通过命令行形式被调用&#xff0c;其精确执行路径位于M…

计算机网络通信基础概念

目录 1、网络通信的本质 2、网络的发展 3、网络协议&#xff08;TCP\IP协议&#xff09; 3.1 协议实现通信的原理 3.2 协议的具体概念 3.3 协议的模型 4、数据链路层 5、网络协议栈和操作系统的关系 6、网络协议通信过程 6.1 通信过程的封装与解包 7、以太网通信…

Ai绘画变现的14种途径 学习Stablediffusion midjourney用途

AIGC&#xff0c;一个在当代社会中不可忽视的词汇&#xff0c;指的是利用人工智能技术生成创作内容。近年来&#xff0c;全球范围内涌现出50个热门的AI工具&#xff0c;其中&#xff0c;以140亿次访问量雄踞榜首的“GBT”&#xff0c;无疑是AI领域的领头羊。在这些工具中&#…

DETR目标检测模型训练自己的数据集

前言 基础环境&#xff1a;ubuntu20.04、python3.8、pytorch:1.10.0、CUDA:11.3 代码地址&#xff1a;https://github.com/facebookresearch/detr 目录 一、训练准备1、预训练模型下载2、txt文件转为coco模式 二、修改训练模型参数三、开始训练四、实现DETR的推理 一、训练准备…

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案 一&#xff0c; 文章简介二&#xff0c;硬件平台构建2.1 音频源板2.2 音频收发板2.3 双板硬件连接 三&#xff0c;软件方案与软件实现3.1 方案实现3.2 软件代码实现3.2.1 4路I2S接收3.2.2 I2S DMA pingpong配置3.2.3 音频数…