Java属性解析映射到Json

说明

将Java属性解析映射到Json属性。

这是另一个用于Spring的Maven插件项目的一部分,因为比较独立,所以先把这部分功能拆出来,可以用于其它项目。


代码

JsonType类

java 复制代码
package com.example.study.constant;

/**
 * JSON类型
 */
public class JsonType {
    public static String STRING = "String";

    public static String NUMBER = "Number";

    public static String BOOLEAN = "Boolean";

    public static String NULL = "Null";

    public static String OBJECT = "Object";

    public static String ARRAY = "Array";

    /**
     * 不支持的类型,如二进制等
     */
    public static String UNSUPPORTED = "UNSUPPORTED";
}

JavaType类

java 复制代码
package com.example.study.constant;

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.temporal.Temporal;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Pattern;

@Slf4j
public class JavaType {
    private static final Set<Class<?>> SIMPLE_TYPES = Set.of(
            Boolean.class,
            Byte.class,
            Character.class,
            Double.class,
            Float.class,
            Integer.class,
            Long.class,
            Short.class,
            Void.class,
            Void.TYPE,

            URI.class,
            URL.class,
            UUID.class,
            Locale.class,
            Pattern.class,
            Class.class);

    private static final Set<Class<?>> SIMPLE_TYPE_ASSIGNABLE_FROMS = Set.of(
            Enum.class,
            CharSequence.class,
            Number.class,
            Date.class,
            Temporal.class,
            ZoneId.class,
            TimeZone.class,
            File.class,
            Path.class,
            Charset.class,
            Currency.class,
            InetAddress.class,

            InputStream.class,
            OutputStream.class,
            ServletRequest.class,
            ServletResponse.class);

    public static Map<String, String> JAVA_JSON_TYPE_MAP = new HashMap<>();

    static {
        JAVA_JSON_TYPE_MAP.put("byte", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("short", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("int", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("long", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("char", JsonType.STRING);
        JAVA_JSON_TYPE_MAP.put("boolean", JsonType.BOOLEAN);
        JAVA_JSON_TYPE_MAP.put("float", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("double", JsonType.NUMBER);

        JAVA_JSON_TYPE_MAP.put("java.lang.Byte", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.lang.Short", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.lang.Integer", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.lang.Long", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.lang.Character", JsonType.STRING);
        JAVA_JSON_TYPE_MAP.put("java.lang.Boolean", JsonType.BOOLEAN);
        JAVA_JSON_TYPE_MAP.put("java.lang.Float", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.lang.Double", JsonType.NUMBER);

        JAVA_JSON_TYPE_MAP.put("java.lang.String", JsonType.STRING);

        JAVA_JSON_TYPE_MAP.put("java.math.BigInteger", JsonType.NUMBER);
        JAVA_JSON_TYPE_MAP.put("java.math.BigDecimal", JsonType.NUMBER);
    }

    /**
     * 判断是否是基本类型
     *
     * @param clazz 类class
     * @return true=基本类型/false=包装类or对象
     */
    public static boolean isBaseType(Class<?> clazz) {
        if (clazz == null) {
            return false;
        }
        return clazz.isPrimitive();
    }

    /**
     * 判断是否是简单类型
     *
     * @param clazz 类class
     * @return true=简单类型/false=复杂类型
     */
    public static boolean isSimpleType(Class<?> clazz) {
        if (clazz == null) {
            return false;
        }
        if (clazz.isPrimitive() || SIMPLE_TYPES.contains(clazz)) {
            return true;
        }
        for (Class<?> assignableFrom : SIMPLE_TYPE_ASSIGNABLE_FROMS) {
            if (assignableFrom.isAssignableFrom(clazz)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 返回java类型对应的json类型
     *
     * @param clazz 类class
     * @return json类型
     */
    public static String getJsonType(Class<?> clazz) {
        if (clazz == null) {
            return JsonType.NULL;
        }
        if (isMap(clazz)) {
            return JsonType.OBJECT;
        }
        if (isCollection(clazz) || clazz.isArray()) {
            return JsonType.ARRAY;
        }
        String jsonType = JAVA_JSON_TYPE_MAP.get(clazz.getName());
        if (jsonType != null) {
            return jsonType;
        }
        if (Number.class.isAssignableFrom(clazz)) {
            return JsonType.NUMBER;
        }
        if (Boolean.class.isAssignableFrom(clazz)) {
            return JsonType.BOOLEAN;
        }
        if (InputStream.class.isAssignableFrom(clazz)
                || OutputStream.class.isAssignableFrom(clazz)
                || ServletRequest.class.isAssignableFrom(clazz)
                || ServletResponse.class.isAssignableFrom(clazz)) {
            return JsonType.UNSUPPORTED;
        }
        return isSimpleType(clazz) ? JsonType.STRING : JsonType.OBJECT;
    }

    /**
     * 判断是否是Map
     *
     * @param clazz 类class
     * @return true=是Map/false=不是Map
     */
    public static boolean isMap(Class<?> clazz) {
        if (clazz == null) {
            return false;
        }
        return Map.class.isAssignableFrom(clazz);
    }

    /**
     * 判断是否是Collection
     *
     * @param clazz 类class
     * @return true=是Collection/false=不是Collection
     */
    public static boolean isCollection(Class<?> clazz) {
        if (clazz == null) {
            return false;
        }
        return Collection.class.isAssignableFrom(clazz);
    }
}

对象属性类PropertyInfo

java 复制代码
package com.example.study.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

/**
 * 对象属性
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PropertyInfo {
    /**
     * 变量名称(不考虑三方json库注解的情况下,也是json字段名
     */
    private String name;

    /**
     * java类型
     */
    private String javaType;

    /**
     * json类型
     */
    private String jsonType;

    /**
     * 子属性
     */
    private List<PropertyInfo> params;
}

对象属性解析器ObjectParser

java 复制代码
package com.example.study.util;

import com.example.study.constant.JavaType;
import com.example.study.constant.JsonType;
import com.example.study.entity.PropertyInfo;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 对象属性解析器
 */
public class ObjectParser {
    private static final String NO_NAME = "";

    /**
     * 解析POJO,映射Java属性为JSON属性
     *
     * @param clazz 类class
     * @return Java、JSON属性映射
     */
    public static List<PropertyInfo> parseObject(Class<?> clazz) {
        if (clazz == null) {
            return new ArrayList<>();
        }
        List<PropertyInfo> list = new ArrayList<>();
        Set<Class<?>> parsedPojoSet = new HashSet<>();
        for (Field declaredField : clazz.getDeclaredFields()) {
            doParseObject(declaredField.getType(), declaredField, list, parsedPojoSet);
        }
        return list;
    }

    private static void doParseObject(Class<?> clazz, Field field, List<PropertyInfo> list, Set<Class<?>> parsedPojoSet) {
        if (JavaType.isSimpleType(clazz) || JavaType.isMap(clazz) || field == null) {
            PropertyInfo info = PropertyInfo.builder()
                    .name(field == null ? NO_NAME : field.getName())
                    .javaType(clazz.getTypeName())
                    .jsonType(JavaType.getJsonType(clazz))
                    .build();
            list.add(info);
            return;
        }
        Type genericType = field.getGenericType();
        parseByGenericType(genericType, field.getName(), list, parsedPojoSet);
    }

    private static void parseByGenericType(Type genericType, String name, List<PropertyInfo> list, Set<Class<?>> parsedPojoSet) {
        if (genericType instanceof ParameterizedType parameterizedType) {
            // List<List<String>>、List<String[]>
            Type type = parameterizedType.getActualTypeArguments()[0];
            PropertyInfo info = PropertyInfo.builder()
                    .name(name)
                    .javaType(genericType.getTypeName())
                    .jsonType(JsonType.ARRAY)
                    .params(new ArrayList<>())
                    .build();
            list.add(info);
            if (parameterizedType.getActualTypeArguments().length == 0) {
                return;
            }
            parseByGenericType(type, NO_NAME, info.getParams(), parsedPojoSet);
        } else if (genericType instanceof GenericArrayType genericArrayType) {
            // 泛型数组 List<T>[] T[]
            PropertyInfo info = PropertyInfo.builder()
                    .name(name)
                    .javaType(genericType.getTypeName())
                    .jsonType(JsonType.ARRAY)
                    .params(new ArrayList<>())
                    .build();
            list.add(info);
            parseByGenericType(genericArrayType.getGenericComponentType(), NO_NAME, info.getParams(), parsedPojoSet);
        } else if (genericType instanceof WildcardType wildcardType) {
            // ? extends Object
            Type[] upperBounds = wildcardType.getUpperBounds();
            // 虽然到目前为止,通配符最多只能有一个上限,但应该编写此方法以适应多个界限
            if (upperBounds.length == 0) {
                return;
            }
            Type upperBound = upperBounds[0];
            parseByGenericType(upperBound, name, list, parsedPojoSet);
        } else if (genericType instanceof TypeVariable) {
            // 泛型,如T K V E等
            list.add(PropertyInfo.builder()
                    .name(NO_NAME)
                    .javaType(genericType.getTypeName())
                    .jsonType(JsonType.OBJECT)
                    .build());
        } else if (genericType instanceof Class<?> clazz) {
            // 一般类型。数组、对象等
            if (clazz.isArray()) {
                PropertyInfo info = PropertyInfo.builder()
                        .name(name)
                        .javaType(genericType.getTypeName())
                        .jsonType(JsonType.ARRAY)
                        .params(new ArrayList<>())
                        .build();
                list.add(info);
                parseByGenericType(clazz.getComponentType(), NO_NAME, info.getParams(), parsedPojoSet);
            } else if (!JavaType.isSimpleType(clazz)) {
                boolean isCollection = JavaType.isCollection(clazz);
                boolean alreadyParsed = parsedPojoSet.contains(clazz);
                if (alreadyParsed || isCollection) {
                    String javaType = alreadyParsed ? "$ref:" + genericType.getTypeName() : genericType.getTypeName();
                    list.add(PropertyInfo.builder()
                            .name(name)
                            .javaType(javaType)
                            .jsonType(isCollection ? JsonType.ARRAY : JsonType.OBJECT)
                            .build());
                    return;
                }

                PropertyInfo info = PropertyInfo.builder()
                        .name(name)
                        .javaType(genericType.getTypeName())
                        .jsonType(JsonType.OBJECT)
                        .params(new ArrayList<>())
                        .build();
                list.add(info);

                if (isPojo(clazz)) {
                    parsedPojoSet.add(clazz);
                }

                for (Field declaredField : clazz.getDeclaredFields()) {
                    if (declaredField.getName().startsWith("this$")) {
                        continue;
                    }
                    doParseObject(declaredField.getType(), declaredField, info.getParams(), parsedPojoSet);
                }
            } else {
                doParseObject(clazz, null, list, parsedPojoSet);
            }
        }
    }

    private static boolean isPojo(Class<?> type) {
        return !(JavaType.isSimpleType(type)
                || Object.class.equals(type)
                || JavaType.isCollection(type)
                || JavaType.isMap(type));
    }
}
相关推荐
先做个垃圾出来………1 天前
Python位运算及操作
java·前端·python
你怎么知道我是队长1 天前
C语言---字符串
java·c语言·算法
rannn_1111 天前
【Java项目】中北大学Java大作业|电商平台
java·git·后端·课程设计·中北大学
资生算法程序员_畅想家_剑魔1 天前
Java常见技术分享-26-事务安全-锁机制-常见的锁实现
java·开发语言
座山雕~1 天前
spring
java·后端·spring
草原印象1 天前
Spring、SpringMVC、Mybatis框架整合实战视频课程
java·spring·mybatis
乌日尼乐1 天前
【Java】IO流完全指南
java·后端
zhaokuner1 天前
14-有界上下文-DDD领域驱动设计
java·开发语言·设计模式·架构
信码由缰1 天前
停止编写Excel规格文档:企业级Java开发的Markdown先行方法
java·ai编程·markdown