Json结构解析比较

news2024/11/24 3:45:44

文章目录

  • 前言
  • 正文
    • 一、项目简介
    • 二、核心代码
      • 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
     */
    @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);
            }
        }
    }
}


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 比较结果,如果存在差异,则返回差异的节点路径
     */
    @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);
            }
        }
    }
}


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);
    }

    @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

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

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

相关文章

Robot Operating System——Service的同步/异步通信

大纲 Service模式的服务端请求响应函数启动Service停止Service完整代码 Service模式的客户端异步模式的客户端完整代码 同步模式的客户端完整代码 测试长期运行的服务发送请求响应一次的服务发送请求 参考资料 在ROS 2中&#xff0c;除了 《Robot Operating System——topic的…

汇昌联信科技拼多多怎么样?

汇昌联信科技拼多多怎么样?汇昌联信科技是一家专注于提供电子商务解决方案的公司&#xff0c;其业务涉及多个电商平台&#xff0c;其中就包括了国内知名的电商平台——拼多多。对于汇昌联信科技在拼多多上的表现&#xff0c;我们可以从以下几个方面来进行深入的探讨和分析。 一…

智慧校园灵动资源调配系统【SpringBoot+Vue】(Java课设)

客官进来看一眼呗&#xff0c;有惊喜&#xff01;【帮你解决烦恼】&#xff1a;Java课设和计Java毕设太难不会做怎么办&#xff1f; 系统类型 【SpringBootVue】类型的系统 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea 运行效果…

2024.7.22 作业

1.将双向链表和循环链表自己实现一遍&#xff0c;至少要实现创建、增、删、改、查、销毁工作 循环链表 looplinklist.h #ifndef LOOPLINKLIST_H #define LOOPLINKLIST_H#include <myhead.h>typedef int datatype;typedef struct Node {union {int len;datatype data;}…

Jetpack Compose 通过 OkHttp 发送 HTTP 请求的示例

下面是一个使用 Kotlin 和 Jetpack Compose 来演示通过 OkHttp 发送 HTTP 请求的示例。这个示例包括在 Jetpack Compose 中发送一个 GET 请求和一个 POST 请求&#xff0c;并显示结果。 添加okhttp依赖 首先&#xff0c;在你的 build.gradle.kts 文件中添加必要的依赖&#xf…

解决:uniapp 小程序 使用swiper 内部嵌套另外一个拥有左右滑动组件导致滑动冲突

解决办法 在swiper-item 内增加这个属性进行包裹 touchmove.stop <div touchmove.stop><qiun-data-charts type"area" :opts"optsStg" :chartData"dateDataStg" /> </div>

最优化理论与方法-第十讲-对偶理论的基本性质和割平面法

文章目录 1. 向量化拉格朗日对偶函数2. 对偶问题是凹函数3. 对偶问题转换4. 外逼近法4.1 步骤4.2 注意事项 1. 向量化拉格朗日对偶函数 ( D ) max ⁡ d ( λ , μ ) s t . λ i ≥ 0 , i 1 , ⋯ , m , d ( λ , μ ) min ⁡ x ∈ X { f ( x ) ∑ i 1 m λ i g i ( x ) ∑ …

传神社区|数据集合集第7期|法律NLP数据集合集

自从ChatGPT等大型语言模型&#xff08;Large Language Model, LLM&#xff09;出现以来&#xff0c;其类通用人工智能&#xff08;AGI&#xff09;能力引发了自然语言处理&#xff08;NLP&#xff09;领域的新一轮研究和应用浪潮。尤其是ChatGLM、LLaMA等普通开发者都能运行的…

CrowdStrike更新致850万Windows设备宕机,微软紧急救火!

7月18日&#xff0c;网络安全公司CrowdStrike发布了一次软件更新&#xff0c;导致全球大范围Windows系统宕机。 预估CrowdStrike的更新影响了将近850万台Windows设备&#xff0c;多行业服务因此停滞&#xff0c;全球打工人原地放假&#xff0c;坐等吃瓜&#xff0c;网络上爆梗…

TCP并发服务器多线程

1.创建线程‐‐pthread_create int pthread_create( pthread_t *thread, // 线程 ID 无符号长整型 const pthread_attr_t *attr, // 线程属性&#xff0c; NULL void *(*start_routine)(void *), // 线程处理函数 void *arg); // 线程处理函数 参数&#xff1a; pthrea…

EXCEL怎么自动添加表格吗?

第一步&#xff0c;选中需要添加表格的范围 第二步&#xff0c;点击开始&#xff0c;选择条件格式&#xff0c;“使用公式确定要设置格式的单元格” 第三步&#xff0c;编辑规则说明加上<>"" 第四步&#xff0c;点击边框&#xff0c;选择外边框确定即可&#x…

STM32CubeIDE(CAN)

目录 一、概念 1、简述 2、CAN 的几种模式 二、实践 1、环回模式轮询通信 1.1 软件配置 1.2 代码编写 2、环回模式中断通信 2.1 软件配置 2.2 代码编写 一、概念 1、简述 STM32微控制器系列包含多个型号&#xff0c;其中一些型号集成了CAN&#xff08;Controller Are…

用移动硬盘装系统里面资料会全删吗?误装系统怎么办

使用‌移动硬盘装系统是一种可行的选择&#xff0c;尤其是当你需要在多台电脑上使用相同的操作系统时。然而&#xff0c;对于初次尝试的新手来说&#xff0c;可能会有一些疑虑&#xff1a;在将移动硬盘用作系统安装盘后&#xff0c;原有的数据是否会被完全删除&#xff1f;如果…

邮件安全篇:邮件反垃圾系统运作机制简介

1. 什么是邮件反垃圾系统&#xff1f; 邮件反垃圾系统是一种专门设计用于检测、过滤和阻止垃圾邮件的技术解决方案。用于保护用户的邮箱免受未经请求的商业广告、诈骗信息、恶意软件、钓鱼攻击和其他非用户意愿接收的电子邮件的侵扰。 反垃圾系统的常见部署形式 2. 邮件反垃圾…

3GPP眼中的XR及其技术特点

3GPP R18 支持了XR Services。XR需要高数据速率和低延迟通信&#xff0c;这也真是5G可以大展身手的地方。这篇就从3GPP的角度看下XR是什么以及XR有哪些技术特点。 Extended Reality (XR) 是指由计算机技术和可穿戴设备生成的所有现实与虚拟相结合的环境和人机交互技术。 实际上…

【ELK】window下ELK的安装与部署

ELK的安装与部署 1. 下载2. 配置&启动2.1 elasticsarch2.1.1 生成证书2.1.2 生成秘钥2.1.3 将凭证迁移到指定目录2.1.4 改配置2.1.5 启动2.1.6 访问测试2.1.7 生成kibana账号 2.2 kibana2.2.1 改配置2.2.2 启动2.2.3 访问测试 2.3 logstash2.3.1 改配置2.3.2 启动 2.4 file…

SQL injection UNION attacks SQL注入联合查询攻击

通过使用UNION关键字&#xff0c;拼接新的SQL语句从而获得额外的内容&#xff0c;例如 select a,b FROM table1 UNION select c,d FROM table2&#xff0c;可以一次性查询 2行数据&#xff0c;一行是a&#xff0c;b&#xff0c;一行是c&#xff0c;d。 UNION查询必须满足2个条…

Potree在web端显示大型点云模型文件

一、克隆项目代码&#xff08;准备好上网工具&#xff0c;得先有node.js npm 环境&#xff09; git clone https://github.com/potree/potree.git二、依赖安装&#xff08;换淘宝镜像能快一些&#xff09; cd potree npm install三、运行 npm start四、使用样例 打开浏览器…

【Linux学习】常用基本指令

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a;Linux学习 目录 &#x1f308;前言&#x1f525;XShell的一些使用查看Linux主机IP使用XShell登录主机XShell下的复制粘贴 &#x1f525;Linux下常用基本指令ls指令pwd指令cd指定touch指令…

Java:115-Spring Boot的底层原理(下篇)

这里续写上一章博客&#xff08;115章博客&#xff09; SpringBoot视图技术&#xff1a; 支持的视图技术 &#xff1a; 前端模板引擎技术的出现&#xff08;jsp也是&#xff09;&#xff0c;使前端开发人员无需关注后端业务的具体实现&#xff08;jsp中&#xff0c;具体的…