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;