Json结构解析&比较

8,289次阅读
没有评论

共计 6873 个字符,预计需要花费 18 分钟才能阅读完成。

文章目录

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

前言

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

关键改动在于:

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

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

关于实体转 JSON,idea 中可以使用插件 POJO to JSON
Json 结构解析 & 比较
安装插件后,在实体类上右键,选择复制 JSON,就能粘贴到 json 结构了。如下图:
Json 结构解析 & 比较

正文

一、项目简介

本次使用了 maven 项目,需要引入以下依赖:

dependency>
    groupId>org.projectlombokgroupId>
    artifactId>lombokartifactId>
    version>1.18.28version>
    optional>trueoptional>
dependency>

dependency>
	groupId>com.fasterxml.jackson.coregroupId>
	artifactId>jackson-databindartifactId>
	version>2.15.4version>
dependency>

测试用的例子是 一个部门中有多个用户,用户本身的属性(多个爱好、性别枚举、生日日期、入职日期)
Json 结构解析 & 比较

二、核心代码

此次的 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.*;


@Slf4j
public class JavaBeanParser {

    private final Class?> rootClass;

    private static final SetClass?>> SKIP_CLASS_SET = new HashSet>();
    private static final SetClass?>> 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;
    }

    
    @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;
            }
            
            if (type.isArray()) {
                continue;
            }

            
            if (!Collection.class.isAssignableFrom(type)) {
                
                Object fieldObject = type.getDeclaredConstructor().newInstance();
                parseJavaBean(fieldObject);
                declaredField.setAccessible(true);
                declaredField.set(javaBean, fieldObject);
                continue;
            }

            
            Type fieldType = declaredField.getGenericType();
            
            if (fieldType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) fieldType;
                
                Type actualType = parameterizedType.getActualTypeArguments()[0];
                String typeName = actualType.getTypeName();
                log.info("存在集合{} {}", type.getName(), typeName, declaredField.getName());
                ListObject> 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;


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

    
    @SneakyThrows
    public ListString> compare(String itemJson) {
        ObjectMapper objectMapper = new ObjectMapper();
        ListString> result = new ArrayList>();

        
        JsonNode fullStructJsonNode = objectMapper.readTree(fullStructJson);
        MapString, Object> resultMap1 = new LinkedHashMap>(16);
        traverseJsonTreeLeafNode(resultMap1, "", fullStructJsonNode);

        
        JsonNode itemJsonNode = objectMapper.readTree(itemJson);
        MapString, Object> resultMap2 = new LinkedHashMap>(16);
        traverseJsonTreeLeafNode(resultMap2, "", itemJsonNode);

        
        resultMap1.forEach((key, value) -> {
            if (!resultMap2.containsKey(key)) {
                result.add(key);
            }
        });
        return result;
    }


    
    private void traverseJsonTreeLeafNode(MapString, 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) {
                
                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}]}]}";
        ListString> 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 ListHobby> 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 ListUser> users;
    }
}


测试结果

17:22:32.768 [main] INFO org.song.json.JavaBeanParser - 存在集合 java.util.List users
17:22:32.772 [main] INFO org.song.json.JavaBeanParser - 存在集合 java.util.List 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

原文地址: Json 结构解析 & 比较

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-29发表,共计6873字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)