自定义 ConvertUtil 值转换工具类, 利用反射实现自定义类转换枚举(Enum)、日期(Date)等。所有的转换过程高度可自定义。

ConvertUtil 值转换工具类

已实现功能

  • 自定义转换枚举类:依据 Enum 中的 @EnumValue 注解,将值转换为枚举类型
  • 自定义转换日期类:将值转换为 Date 类型,依据 @JsonFormat 注解中的 pattern 进行赋值

依赖

  • hutool
  • jackson
  • mybatisplus

当然,如果你不想依赖 jackson 或 mybatisplus 这两个,只需要把对应的注解名字换一个就可以了~

只是由于项目需求需要入库时自动转换 Enum 枚举类,所以使用了 mybatis-plus 的 @EnumValue 。

ConvertUtil 代码

基于 hutool 工具包实现可自定义的 枚举日期 转换工具类。当然如果有你自己需要转换的数据类型,在里面加入就可以了。

package com.demo.common.core.utils.converter;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.demo.common.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 转换工具类
 * <p> 若需要反序列化一个自定义对象,则可按照如下代码实现
 * <pre>
 *     // 待转换对象类型
 *     Class<?> paramType = Demo.class;
 *     // 待转换值
 *     Object value = "value";
 *
 *     // 获取属性列表
 *     List<Field> fieldList = ConvertUtil.getAllFields(paramType);
 *     // 构建实例
 *     Object result = ConvertUtil.getDefaultValue(paramType);
 *     // 获取所有属性
 *     for (Field field : fieldList) {
 *         // 赋值 (根据你自己的需要设置 value 的值)
 *         ConvertUtil.setFieldValue(result, field, value);
 *     }
 * </pre>
 *
 * @author HeHTao
 * @date 2024/04/03
 */
@Slf4j
public class ConvertUtil {

    /** 基础类型的包装类 */
    private static final List<Class<?>> baseWrapperClasses = Arrays.asList(
            Boolean.class, Character.class, Byte.class, Short.class,
            Integer.class, Long.class, Float.class, Double.class,
            Void.class
    );

    /** 已自定义转换的类 */
    private static final List<Class<?>> convertedClasses = Arrays.asList(
            Object.class, String.class, Collection.class, Array.class, List.class, Map.class, Set.class,
            Enum.class, Date.class, LocalDateTime.class
    );

    /**
     * 设置属性值。
     * <p> 若 obj 是基础类型及其包装类、已自定义转换的类,应使用 convert 方法进行转换。
     *
     * @param obj   被赋值对象
     * @param field 待赋值属性
     * @param value 待赋值的值
     */
    public static void setFieldValue(Object obj, Field field, Object value) {
        // 获取属性
        Object target = convert(field.getType(), value);
        try {
            field.setAccessible(true);
            field.set(obj, target);
        } catch (IllegalAccessException e) {
            throw new UtilException(e, "IllegalAccess for {}.{}", obj.getClass(), field.getName());
        }
    }

    /**
     * 将值转换为指定类型
     *
     * @param type  指定类型
     * @param value 值
     * @return 转换后的值
     * @throws UtilException 转换异常
     */
    @SuppressWarnings("unchecked")
    public static <T> T convert(Class<T> type, Object value) throws UtilException {
        // 检查是否为基础类型的包装类,并在为空时返回null
        if (null == value) {
            if (type.isPrimitive()) {
                // 获取null对应默认值,防止原始类型造成空指针问题
                return getDefaultValue(type);
            }
            return null;
        }

        // 处理 Object[] 类型的值
        if (value instanceof Object[]) {
            Object[] values = (Object[]) value;
            if (type.isArray()) {
                return (T) convertToArray(type.getComponentType(), values);
            } else if (Collection.class.isAssignableFrom(type)) {
                return Convert.convert(type, value);
            } else {
                // 如果不是数组或列表,只取第一个值
                value = values[0];
            }
        }

        // 获取属性默认值
        T target;
        if (type.isEnum()) {
            // Enum 特殊处理
            target = (T) convertToEnum(type, value);
        } else if (LocalDateTime.class.isAssignableFrom(type)) {
            // LocalDateTime 特殊处理
            target = (T) convertToLocalDateTime(type, value);
        } else if (Date.class.isAssignableFrom(type)) {
            // Date 特殊处理
            target = (T) convertToDate(type, value);
        } else {
            // 其他类型转换
            target = Convert.convert(type, value);
        }

        return target;
    }

    /**
     * 将值列表转换为指定类型列表
     *
     * @param type  元素类型
     * @param value 值列表
     * @return 转换后的值
     */
    public static <T, U> List<T> convertToList(Class<T> type, U[] value) throws UtilException {
        List<U> list = Arrays.asList(value);
        return convertToList(type, list);
    }

    /**
     * 将值列表转换为指定类型列表
     *
     * @param type  元素类型
     * @param value 值列表
     * @return 转换后的值
     */
    public static <T, U> List<T> convertToList(Class<T> type, Collection<U> value) throws UtilException {
        List<T> result = new ArrayList<>();
        for (U e : value) {
            result.add(convert(type, e));
        }
        return result;
    }

    /**
     * 将值列表转换为指定类型数组
     *
     * @param type  元素类型
     * @param value 值列表
     * @return 转换后的值
     */
    @SuppressWarnings("unchecked")
    public static <T, U> T[] convertToArray(Class<T> type, U[] value) throws UtilException {
        T[] array = (T[]) Array.newInstance(type, value.length);
        for (int i = 0; i < value.length; i++) {
            array[i] = convert(type, value[i]);
        }
        return array;
    }

    /**
     * 依据 Enum 中的 @EnumValue 注解,将值转换为枚举类型
     *
     * @param type  枚举类型
     * @param value 值
     * @return 转换后的值
     */
    public static Object convertToEnum(Class<?> type, Object value) {
        if (StringUtils.isEmpty(value.toString())) {
            return null;
        } else if (!type.isEnum()) {
            throw new UtilException("Target class is not Enum: {}", type);
        }

        Field enumField = _findEnumValueAnnotationField(type);
        // 提取枚举类型的值,并判断 value 是否为枚举类型的值
        Enum<?>[] enums = (Enum<?>[]) type.getEnumConstants();
        // 选取枚举类型的值
        for (Enum<?> anEnum : enums) {
            Object tmp = ReflectUtil.getFieldValue(anEnum, enumField);
            if (tmp.toString().equals(value.toString())) {
                return anEnum;
            }
        }
        // 枚举类型的值不匹配
        throw new UtilException("Enum value `{}` not match for {}.{}", value, type, enumField.getName());
    }

    /**
     * 将值转换为 LocalDateTime 类型,依据 @JsonFormat 注解中的 pattern 进行赋值
     * <p> 若无 @JsonFormat 注解,则使用默认格式。
     * <p> 默认格式为 yyyy-MM-dd HH:mm:ss
     *
     * @param type LocalDateTime 类型
     * @param value 值
     * @return 转换后的值
     */
    public static Object convertToLocalDateTime(Class<?> type, Object value) {
        String valueStr = value.toString();
        if (StringUtils.isEmpty(valueStr)) {
            return null;
        } else if (!LocalDateTime.class.isAssignableFrom(type)) {
            throw new UtilException("Target class is not LocalDateTime type: {}", type);
        }

        JsonFormat jsonFormat = type.getAnnotation(JsonFormat.class);
        if (null != jsonFormat) {
            String pattern = jsonFormat.pattern();
            if (null != pattern) {
                return DateUtil.parseLocalDateTime(valueStr, pattern);
            } else {
                throw new UtilException("LocalDateTime value `{}` not match for {}", valueStr, type);
            }
        } else {
            // 默认格式
            return DateUtil.parseLocalDateTime(valueStr);
        }
    }

    /**
     * 将值转换为 Date 类型,依据 @JsonFormat 注解中的 pattern 进行赋值
     * <p> 若无 @JsonFormat 注解,则使用默认格式。
     * <p> 默认格式为 yyyy-MM-dd HH:mm:ss
     *
     * @param type  Date 类型
     * @param value 值
     * @return 转换后的值
     */
    public static Object convertToDate(Class<?> type, Object value) {
        String valueStr = value.toString();
        if (StringUtils.isEmpty(valueStr)) {
            return null;
        } else if (!Date.class.isAssignableFrom(type)) {
            throw new UtilException("Target class is not Date type: {}", type);
        }

        JsonFormat jsonFormat = type.getAnnotation(JsonFormat.class);
        if (null != jsonFormat) {
            String pattern = jsonFormat.pattern();
            if (null != pattern) {
                return DateUtil.parse(valueStr, pattern);
            } else {
                throw new UtilException("Date value `{}` not match for {}", valueStr, type);
            }
        } else {
            // 默认格式
            return DateUtil.parse(valueStr);
        }
    }

    /**
     * 获取默认值
     *
     * @param type 类型
     * @param <T>  泛型
     * @return 默认值
     */
    @SuppressWarnings("unchecked")
    public static <T> T getDefaultValue(Class<T> type) {
        Assert.notNull(type);

        // 原始类型
        if (type.isPrimitive()) {
            return (T) ClassUtil.getPrimitiveDefaultValue(type);
        }

        // 某些特殊接口的实例化按照默认实现进行
        if (AbstractMap.class.isAssignableFrom(type)) {
            type = (Class<T>) HashMap.class;
        } else if (List.class.isAssignableFrom(type)) {
            type = (Class<T>) ArrayList.class;
        } else if (Set.class.isAssignableFrom(type)) {
            type = (Class<T>) HashSet.class;
        }

        // 枚举
        if (type.isEnum()) {
            return null;
        }

        // 时间
        if (LocalDateTime.class.isAssignableFrom(type)) {
            return null;
        }
        if (Date.class.isAssignableFrom(type)) {
            return null;
        }

        try {
            return ReflectUtil.newInstance(type);
        } catch (Exception e) {
            // ignore
            // 默认构造不存在的情况下查找其它构造
        }

        // 数组
        if (type.isArray()) {
            return (T) Array.newInstance(type.getComponentType(), 0);
        }

        final Constructor<T>[] constructors = ReflectUtil.getConstructors(type);
        Class<?>[] parameterTypes;
        for (Constructor<T> constructor : constructors) {
            parameterTypes = constructor.getParameterTypes();
            if (0 == parameterTypes.length) {
                continue;
            }
            ReflectUtil.setAccessible(constructor);
            try {
                return constructor.newInstance(ClassUtil.getDefaultValues(parameterTypes));
            } catch (Exception ignore) {
                // 构造出错时继续尝试下一种构造方式
            }
        }
        return null;
    }

    /**
     * 获取所有需要反序列化的属性
     *
     * @param clazz 类
     * @return 属性列表
     */
    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();  // 需要反序列化的属性

        while (clazz != null) {
            if (isConvertedClass(clazz)) {
                // 基础类型的包装,以及已经自定义转换的类应直接反序列化,不能获取其属性
                log.warn("Converted class: {} - 基础类型及其包装类,以及已经自定义转换的类应直接反序列化,不能获取其属性", clazz.getName());
                return fields;
            }

            for (Field field : clazz.getDeclaredFields()) {
                if (isExcludeField(field)) {
                    continue;
                }
                // 递归获取成员类的属性
                if (isConvertedClass(field.getType())) {
                    fields.add(field);
                } else {
                    fields.addAll(getAllFields(field.getType()));
                }
            }
            clazz = clazz.getSuperclass();
        }

        return fields;
    }

    /**
     * 判断是否为基础类型的包装类或基元类型
     *
     * @param clazz 类
     * @return 是否为基础类型的包装类或基元类型
     */
    public static boolean isPrimitive(Class<?> clazz) {
        return clazz.isPrimitive() || baseWrapperClasses.contains(clazz);
    }

    /**
     * 判断是否为可进行转换的类
     *
     * @param clazz
     * @return
     */
    public static boolean isConvertedClass(Class<?> clazz) {
        return isPrimitive(clazz)
                || clazz.isArray() || clazz.isEnum()
                || Collection.class.isAssignableFrom(clazz)
                || convertedClasses.contains(clazz);
    }

    /**
     * 在反序列化时需要排除的类属性
     * <p> 排除 final 修饰的属性,排除接口
     * <p> 排除匿名类、局部类、合成类
     *
     * @param field 属性
     * @return 是否需要排除
     */
    public static boolean isExcludeField(Field field) {
        return Modifier.isFinal(field.getModifiers())
                || Modifier.isInterface(field.getModifiers())
                || field.getType().isAnonymousClass()
                || field.getType().isLocalClass()
                || field.getType().isSynthetic();
    }

    /**
     * 查找标记 @EnumValue 的字段
     *
     * @param clazz class
     * @return EnumValue字段
     */
    private static Field _findEnumValueAnnotationField(Class<?> clazz) {
        Optional<Field> fieldOptional = Arrays.stream(clazz.getDeclaredFields())
                .filter(field ->
                        field.isAnnotationPresent(EnumValue.class))
                .findFirst();
        return fieldOptional
                .orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", clazz.getName())));
    }

    private ConvertUtil() {
        // 静态类不可实例化
    }

}

枚举类使用注意事项

需要在识别的枚举字段中加入mybatisplus 包中的 @EnumValue 注解。

转换类会根据被注解标注的字段值进行匹配转换。

package com.demo.common.core.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author HeHTao
 * @description 账单类型
 * @date 2024/04/01
 */
@Getter
@AllArgsConstructor
public enum BillStatuEnum {
    NEW_BILL(1, "新购订单"),
    UPGRADE_BILL(2, "升级订单"),
    DOWNGRADE_BILL(3, "降级订单"),
    UNSUBSCRIBE_BILL(4, "退订订单"),
    RENEW_BILL(5, "续订订单"),
    // 用于云公司月收入分摊
    UNSUBSCRIBE_RENEW_CYCLE(14, "退订续费周期"),
    AMOUNT_TO_YEAR_MONTH(15, "按量转包年/包月"),
    YEAR_MONTH_TO_AMOUNT(16, "包年/包月转按量");

    @EnumValue
    @JsonValue
    private final int code;
    private final String description;
}

加完 @EnumValue 注解后,记得在 yaml 文件中添加 MyBatis 的配置扫描。(mybatis-plus 3.5.2 之后的版本可以不配置该项)

mybatis-plus:
  type-enums-package: com.demo.enums

日期类使用注意事项

日期类需要在类定义字段的时候加入 jackson 的 @JsonFormat 字段,并填入 pattern 值用以匹配日期格式。

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date serviceEndTime;
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeHTao_3381

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值