mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge branch 'v6-dev' of gitee.com:dromara/hutool into v6-dev
This commit is contained in:
commit
5a3e1cd8f6
@ -54,109 +54,6 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public class BeanUtil {
|
public class BeanUtil {
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为可读的Bean对象,判定方法是:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 1、是否存在只有无参数的getXXX方法或者isXXX方法
|
|
||||||
* 2、是否存在public类型的字段
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param clazz 待测试类
|
|
||||||
* @return 是否为可读的Bean对象
|
|
||||||
* @see #hasGetter(Class)
|
|
||||||
* @see #hasPublicField(Class)
|
|
||||||
*/
|
|
||||||
public static boolean isReadableBean(final Class<?> clazz) {
|
|
||||||
if (null == clazz) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasGetter(clazz) || hasPublicField(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为可写Bean对象,判定方法是:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 1、是否存在只有一个参数的setXXX方法
|
|
||||||
* 2、是否存在public类型的字段
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param clazz 待测试类
|
|
||||||
* @return 是否为Bean对象
|
|
||||||
* @see #hasSetter(Class)
|
|
||||||
* @see #hasPublicField(Class)
|
|
||||||
*/
|
|
||||||
public static boolean isWritableBean(final Class<?> clazz) {
|
|
||||||
if (null == clazz) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasSetter(clazz) || hasPublicField(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否有Setter方法<br>
|
|
||||||
* 判定方法是否存在只有一个参数的setXXX方法
|
|
||||||
*
|
|
||||||
* @param clazz 待测试类
|
|
||||||
* @return 是否为Bean对象
|
|
||||||
* @since 4.2.2
|
|
||||||
*/
|
|
||||||
public static boolean hasSetter(final Class<?> clazz) {
|
|
||||||
if (ClassUtil.isNormalClass(clazz)) {
|
|
||||||
for (final Method method : clazz.getMethods()) {
|
|
||||||
if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
|
|
||||||
// 检测包含标准的setXXX方法即视为标准的JavaBean
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否为Bean对象<br>
|
|
||||||
* 判定方法是否存在只有无参数的getXXX方法或者isXXX方法
|
|
||||||
*
|
|
||||||
* @param clazz 待测试类
|
|
||||||
* @return 是否为Bean对象
|
|
||||||
* @since 4.2.2
|
|
||||||
*/
|
|
||||||
public static boolean hasGetter(final Class<?> clazz) {
|
|
||||||
if (ClassUtil.isNormalClass(clazz)) {
|
|
||||||
for (final Method method : clazz.getMethods()) {
|
|
||||||
if (method.getParameterCount() == 0) {
|
|
||||||
final String name = method.getName();
|
|
||||||
if (name.startsWith("get") || name.startsWith("is")) {
|
|
||||||
if (!"getClass".equals(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指定类中是否有public类型字段(static字段除外)
|
|
||||||
*
|
|
||||||
* @param clazz 待测试类
|
|
||||||
* @return 是否有public类型字段
|
|
||||||
* @since 5.1.0
|
|
||||||
*/
|
|
||||||
public static boolean hasPublicField(final Class<?> clazz) {
|
|
||||||
if (ClassUtil.isNormalClass(clazz)) {
|
|
||||||
for (final Field field : clazz.getFields()) {
|
|
||||||
if (ModifierUtil.isPublic(field) && !ModifierUtil.isStatic(field)) {
|
|
||||||
//非static的public字段
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建动态Bean
|
* 创建动态Bean
|
||||||
*
|
*
|
||||||
@ -200,8 +97,7 @@ public class BeanUtil {
|
|||||||
getBeanDesc(clazz).getProps().forEach(action);
|
getBeanDesc(clazz).getProps().forEach(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------------- PropertyDescriptor
|
// region ----- getPropertyDescriptor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得Bean字段描述数组
|
* 获得Bean字段描述数组
|
||||||
*
|
*
|
||||||
@ -278,6 +174,7 @@ public class BeanUtil {
|
|||||||
final Map<String, PropertyDescriptor> map = getPropertyDescriptorMap(clazz, ignoreCase);
|
final Map<String, PropertyDescriptor> map = getPropertyDescriptorMap(clazz, ignoreCase);
|
||||||
return (null == map) ? null : map.get(fieldName);
|
return (null == map) ? null : map.get(fieldName);
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得字段值,通过反射直接获得字段值,并不调用getXXX方法<br>
|
* 获得字段值,通过反射直接获得字段值,并不调用getXXX方法<br>
|
||||||
@ -380,101 +277,7 @@ public class BeanUtil {
|
|||||||
BeanPath.of(expression).setValue(bean, value);
|
BeanPath.of(expression).setValue(bean, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------- mapToBean
|
// region ----- toBean
|
||||||
|
|
||||||
/**
|
|
||||||
* Map转换为Bean对象
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map {@link Map}
|
|
||||||
* @param beanClass Bean Class
|
|
||||||
* @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格
|
|
||||||
* @param copyOptions 转Bean选项
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public static <T> T mapToBean(final Map<?, ?> map, final Class<T> beanClass, final boolean isToCamelCase, final CopyOptions copyOptions) {
|
|
||||||
return fillBeanWithMap(map, ConstructorUtil.newInstanceIfPossible(beanClass), isToCamelCase, copyOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------- fillBeanWithMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用Map填充Bean对象
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map Map
|
|
||||||
* @param bean Bean
|
|
||||||
* @param isIgnoreError 是否忽略注入错误
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public static <T> T fillBeanWithMap(final Map<?, ?> map, final T bean, final boolean isIgnoreError) {
|
|
||||||
return fillBeanWithMap(map, bean, false, isIgnoreError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用Map填充Bean对象,可配置将下划线转换为驼峰
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map Map
|
|
||||||
* @param bean Bean
|
|
||||||
* @param isToCamelCase 是否将下划线模式转换为驼峰模式
|
|
||||||
* @param isIgnoreError 是否忽略注入错误
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public static <T> T fillBeanWithMap(final Map<?, ?> map, final T bean, final boolean isToCamelCase, final boolean isIgnoreError) {
|
|
||||||
return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.of().setIgnoreError(isIgnoreError));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用Map填充Bean对象,忽略大小写
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map Map
|
|
||||||
* @param bean Bean
|
|
||||||
* @param isIgnoreError 是否忽略注入错误
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public static <T> T fillBeanWithMapIgnoreCase(final Map<?, ?> map, final T bean, final boolean isIgnoreError) {
|
|
||||||
return fillBeanWithMap(map, bean, CopyOptions.of().setIgnoreCase(true).setIgnoreError(isIgnoreError));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用Map填充Bean对象
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map Map
|
|
||||||
* @param bean Bean
|
|
||||||
* @param copyOptions 属性复制选项 {@link CopyOptions}
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public static <T> T fillBeanWithMap(final Map<?, ?> map, final T bean, final CopyOptions copyOptions) {
|
|
||||||
return fillBeanWithMap(map, bean, false, copyOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用Map填充Bean对象
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param map Map
|
|
||||||
* @param bean Bean
|
|
||||||
* @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格
|
|
||||||
* @param copyOptions 属性复制选项 {@link CopyOptions}
|
|
||||||
* @return Bean
|
|
||||||
* @since 3.3.1
|
|
||||||
*/
|
|
||||||
public static <T> T fillBeanWithMap(Map<?, ?> map, final T bean, final boolean isToCamelCase, final CopyOptions copyOptions) {
|
|
||||||
if (MapUtil.isEmpty(map)) {
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
if (isToCamelCase) {
|
|
||||||
map = MapUtil.toCamelCaseMap(map);
|
|
||||||
}
|
|
||||||
copyProperties(map, bean, copyOptions);
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------- fillBean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对象或Map转Bean
|
* 对象或Map转Bean
|
||||||
*
|
*
|
||||||
@ -520,7 +323,9 @@ public class BeanUtil {
|
|||||||
copyProperties(source, target, options);
|
copyProperties(source, target, options);
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region ----- fillBean
|
||||||
/**
|
/**
|
||||||
* 填充Bean的核心方法
|
* 填充Bean的核心方法
|
||||||
*
|
*
|
||||||
@ -537,8 +342,25 @@ public class BeanUtil {
|
|||||||
|
|
||||||
return BeanCopier.of(valueProvider, bean, copyOptions).copy();
|
return BeanCopier.of(valueProvider, bean, copyOptions).copy();
|
||||||
}
|
}
|
||||||
// --------------------------------------------------------------------------------------------- beanToMap
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用Map填充Bean对象
|
||||||
|
*
|
||||||
|
* @param <T> Bean类型
|
||||||
|
* @param map Map
|
||||||
|
* @param bean Bean
|
||||||
|
* @param copyOptions 属性复制选项 {@link CopyOptions}
|
||||||
|
* @return Bean
|
||||||
|
*/
|
||||||
|
public static <T> T fillBeanWithMap(final Map<?, ?> map, final T bean, final CopyOptions copyOptions) {
|
||||||
|
if (MapUtil.isEmpty(map)) {
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
return copyProperties(map, bean, copyOptions);
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region ----- beanToMap
|
||||||
/**
|
/**
|
||||||
* 将bean的部分属性转换成map<br>
|
* 将bean的部分属性转换成map<br>
|
||||||
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。
|
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。
|
||||||
@ -657,8 +479,9 @@ public class BeanUtil {
|
|||||||
|
|
||||||
return BeanCopier.of(bean, targetMap, copyOptions).copy();
|
return BeanCopier.of(bean, targetMap, copyOptions).copy();
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------- copyProperties
|
// region ----- copyProperties
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按照Bean对象属性创建对应的Class对象,并忽略某些属性
|
* 按照Bean对象属性创建对应的Class对象,并忽略某些属性
|
||||||
@ -727,6 +550,20 @@ public class BeanUtil {
|
|||||||
return BeanCopier.of(source, target, ObjUtil.defaultIfNull(copyOptions, CopyOptions::of)).copy();
|
return BeanCopier.of(source, target, ObjUtil.defaultIfNull(copyOptions, CopyOptions::of)).copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制集合中的Bean属性<br>
|
||||||
|
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
|
||||||
|
*
|
||||||
|
* @param collection 原Bean集合
|
||||||
|
* @param targetType 目标Bean类型
|
||||||
|
* @param <T> Bean类型
|
||||||
|
* @return 复制后的List
|
||||||
|
* @since 5.6.6
|
||||||
|
*/
|
||||||
|
public static <T> List<T> copyToList(final Collection<?> collection, final Class<T> targetType) {
|
||||||
|
return copyToList(collection, targetType, CopyOptions.of());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 复制集合中的Bean属性<br>
|
* 复制集合中的Bean属性<br>
|
||||||
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
|
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
|
||||||
@ -758,20 +595,7 @@ public class BeanUtil {
|
|||||||
return target;
|
return target;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
/**
|
|
||||||
* 复制集合中的Bean属性<br>
|
|
||||||
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
|
|
||||||
*
|
|
||||||
* @param collection 原Bean集合
|
|
||||||
* @param targetType 目标Bean类型
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @return 复制后的List
|
|
||||||
* @since 5.6.6
|
|
||||||
*/
|
|
||||||
public static <T> List<T> copyToList(final Collection<?> collection, final Class<T> targetType) {
|
|
||||||
return copyToList(collection, targetType, CopyOptions.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 给定的Bean的类名是否匹配指定类名字符串<br>
|
* 给定的Bean的类名是否匹配指定类名字符串<br>
|
||||||
@ -881,6 +705,110 @@ public class BeanUtil {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为可读的Bean对象,判定方法是:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1、是否存在只有无参数的getXXX方法或者isXXX方法
|
||||||
|
* 2、是否存在public类型的字段
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param clazz 待测试类
|
||||||
|
* @return 是否为可读的Bean对象
|
||||||
|
* @see #hasGetter(Class)
|
||||||
|
* @see #hasPublicField(Class)
|
||||||
|
*/
|
||||||
|
public static boolean isReadableBean(final Class<?> clazz) {
|
||||||
|
if (null == clazz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hasGetter(clazz) || hasPublicField(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为可写Bean对象,判定方法是:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1、是否存在只有一个参数的setXXX方法
|
||||||
|
* 2、是否存在public类型的字段
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param clazz 待测试类
|
||||||
|
* @return 是否为Bean对象
|
||||||
|
* @see #hasSetter(Class)
|
||||||
|
* @see #hasPublicField(Class)
|
||||||
|
*/
|
||||||
|
public static boolean isWritableBean(final Class<?> clazz) {
|
||||||
|
if (null == clazz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hasSetter(clazz) || hasPublicField(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// region ----- hasXXX
|
||||||
|
/**
|
||||||
|
* 判断是否有Setter方法<br>
|
||||||
|
* 判定方法是否存在只有一个参数的setXXX方法
|
||||||
|
*
|
||||||
|
* @param clazz 待测试类
|
||||||
|
* @return 是否为Bean对象
|
||||||
|
* @since 4.2.2
|
||||||
|
*/
|
||||||
|
public static boolean hasSetter(final Class<?> clazz) {
|
||||||
|
if (ClassUtil.isNormalClass(clazz)) {
|
||||||
|
for (final Method method : clazz.getMethods()) {
|
||||||
|
if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
|
||||||
|
// 检测包含标准的setXXX方法即视为标准的JavaBean
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为Bean对象<br>
|
||||||
|
* 判定方法是否存在只有无参数的getXXX方法或者isXXX方法
|
||||||
|
*
|
||||||
|
* @param clazz 待测试类
|
||||||
|
* @return 是否为Bean对象
|
||||||
|
* @since 4.2.2
|
||||||
|
*/
|
||||||
|
public static boolean hasGetter(final Class<?> clazz) {
|
||||||
|
if (ClassUtil.isNormalClass(clazz)) {
|
||||||
|
for (final Method method : clazz.getMethods()) {
|
||||||
|
if (method.getParameterCount() == 0) {
|
||||||
|
final String name = method.getName();
|
||||||
|
if (name.startsWith("get") || name.startsWith("is")) {
|
||||||
|
if (!"getClass".equals(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定类中是否有public类型字段(static字段除外)
|
||||||
|
*
|
||||||
|
* @param clazz 待测试类
|
||||||
|
* @return 是否有public类型字段
|
||||||
|
* @since 5.1.0
|
||||||
|
*/
|
||||||
|
public static boolean hasPublicField(final Class<?> clazz) {
|
||||||
|
if (ClassUtil.isNormalClass(clazz)) {
|
||||||
|
for (final Field field : clazz.getFields()) {
|
||||||
|
if (ModifierUtil.isPublic(field) && !ModifierUtil.isStatic(field)) {
|
||||||
|
//非static的public字段
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断Bean是否包含值为{@code null}的属性<br>
|
* 判断Bean是否包含值为{@code null}的属性<br>
|
||||||
* 对象本身为{@code null}也返回true
|
* 对象本身为{@code null}也返回true
|
||||||
@ -912,6 +840,7 @@ public class BeanUtil {
|
|||||||
&& StrUtil.isEmptyIfStr(FieldUtil.getFieldValue(bean, field))
|
&& StrUtil.isEmptyIfStr(FieldUtil.getFieldValue(bean, field))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查Bean<br>
|
* 检查Bean<br>
|
||||||
|
@ -86,7 +86,7 @@ public class BeanToBeanCopier<S, T> extends AbsCopier<S, T> {
|
|||||||
|
|
||||||
// 检查目标字段可写性
|
// 检查目标字段可写性
|
||||||
// 目标字段检查放在键值对编辑之后,因为键可能被编辑修改
|
// 目标字段检查放在键值对编辑之后,因为键可能被编辑修改
|
||||||
final PropDesc tDesc = targetPropDescMap.get(sFieldName);
|
final PropDesc tDesc = this.copyOptions.findPropDesc(targetPropDescMap, sFieldName);
|
||||||
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
|
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
|
||||||
// 字段不可写,跳过之
|
// 字段不可写,跳过之
|
||||||
return;
|
return;
|
||||||
|
@ -12,12 +12,14 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.core.bean.copier;
|
package org.dromara.hutool.core.bean.copier;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.bean.PropDesc;
|
||||||
import org.dromara.hutool.core.convert.Convert;
|
import org.dromara.hutool.core.convert.Convert;
|
||||||
import org.dromara.hutool.core.convert.Converter;
|
import org.dromara.hutool.core.convert.Converter;
|
||||||
import org.dromara.hutool.core.func.LambdaUtil;
|
import org.dromara.hutool.core.func.LambdaUtil;
|
||||||
import org.dromara.hutool.core.func.SerFunction;
|
import org.dromara.hutool.core.func.SerFunction;
|
||||||
import org.dromara.hutool.core.lang.mutable.MutableEntry;
|
import org.dromara.hutool.core.lang.mutable.MutableEntry;
|
||||||
import org.dromara.hutool.core.array.ArrayUtil;
|
import org.dromara.hutool.core.array.ArrayUtil;
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -76,6 +78,11 @@ public class CopyOptions implements Serializable {
|
|||||||
*/
|
*/
|
||||||
protected boolean override = true;
|
protected boolean override = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否自动转换为驼峰方式
|
||||||
|
*/
|
||||||
|
protected boolean autoTransCamelCase = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义类型转换器,默认使用全局万能转换器转换
|
* 自定义类型转换器,默认使用全局万能转换器转换
|
||||||
*/
|
*/
|
||||||
@ -178,7 +185,7 @@ public class CopyOptions implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public CopyOptions setIgnoreProperties(final String... ignoreProperties) {
|
public CopyOptions setIgnoreProperties(final String... ignoreProperties) {
|
||||||
return setPropertiesFilter((field, o) -> {
|
return setPropertiesFilter((field, o) -> {
|
||||||
if(ignoreCase){
|
if (ignoreCase) {
|
||||||
// issue#I80FP4
|
// issue#I80FP4
|
||||||
return !ArrayUtil.containsIgnoreCase(ignoreProperties, field.getName());
|
return !ArrayUtil.containsIgnoreCase(ignoreProperties, field.getName());
|
||||||
}
|
}
|
||||||
@ -311,6 +318,19 @@ public class CopyOptions implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否自动转换为驼峰方式<br>
|
||||||
|
* 一般用于map转bean和bean转bean出现非驼峰格式时,在尝试转换失败的情况下,是否二次检查转为驼峰匹配
|
||||||
|
*
|
||||||
|
* @param autoTransCamelCase 是否自动转换为驼峰方式
|
||||||
|
* @return this
|
||||||
|
* @since 5.8.25
|
||||||
|
*/
|
||||||
|
public CopyOptions setAutoTransCamelCase(final boolean autoTransCamelCase) {
|
||||||
|
this.autoTransCamelCase = autoTransCamelCase;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置自定义类型转换器,默认使用全局万能转换器转换。
|
* 设置自定义类型转换器,默认使用全局万能转换器转换。
|
||||||
*
|
*
|
||||||
@ -348,4 +368,25 @@ public class CopyOptions implements Serializable {
|
|||||||
protected boolean testPropertyFilter(final Field field, final Object value) {
|
protected boolean testPropertyFilter(final Field field, final Object value) {
|
||||||
return null == this.propertiesFilter || this.propertiesFilter.test(field, value);
|
return null == this.propertiesFilter || this.propertiesFilter.test(field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找Map对应Bean的名称<br>
|
||||||
|
* 尝试原名称、转驼峰名称、isXxx去掉is的名称
|
||||||
|
*
|
||||||
|
* @param targetPropDescMap 目标bean的属性描述Map
|
||||||
|
* @param sKeyStr 键或字段名
|
||||||
|
* @return {@link PropDesc}
|
||||||
|
*/
|
||||||
|
protected PropDesc findPropDesc(final Map<String, PropDesc> targetPropDescMap, final String sKeyStr) {
|
||||||
|
PropDesc propDesc = targetPropDescMap.get(sKeyStr);
|
||||||
|
// 转驼峰尝试查找
|
||||||
|
if (null == propDesc && this.autoTransCamelCase) {
|
||||||
|
final String camelCaseKey = StrUtil.toCamelCase(sKeyStr);
|
||||||
|
if (!StrUtil.equals(sKeyStr, camelCaseKey)) {
|
||||||
|
// 只有转换为驼峰后与原key不同才重复查询,相同说明本身就是驼峰,不需要二次查询
|
||||||
|
propDesc = targetPropDescMap.get(camelCaseKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return propDesc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
|
|||||||
|
|
||||||
// 检查目标字段可写性
|
// 检查目标字段可写性
|
||||||
// 目标字段检查放在键值对编辑之后,因为键可能被编辑修改
|
// 目标字段检查放在键值对编辑之后,因为键可能被编辑修改
|
||||||
final PropDesc tDesc = findPropDesc(targetPropDescMap, sFieldName);
|
final PropDesc tDesc = this.copyOptions.findPropDesc(targetPropDescMap, sFieldName);
|
||||||
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
|
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
|
||||||
// 字段不可写,跳过之
|
// 字段不可写,跳过之
|
||||||
return;
|
return;
|
||||||
@ -109,24 +109,4 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
|
|||||||
});
|
});
|
||||||
return this.target;
|
return this.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找Map对应Bean的名称<br>
|
|
||||||
* 尝试原名称、转驼峰名称、isXxx去掉is的名称
|
|
||||||
*
|
|
||||||
* @param targetPropDescMap 目标bean的属性描述Map
|
|
||||||
* @param sKeyStr 键或字段名
|
|
||||||
* @return {@link PropDesc}
|
|
||||||
*/
|
|
||||||
private PropDesc findPropDesc(final Map<String, PropDesc> targetPropDescMap, String sKeyStr){
|
|
||||||
PropDesc propDesc = targetPropDescMap.get(sKeyStr);
|
|
||||||
if(null != propDesc){
|
|
||||||
return propDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转驼峰尝试查找
|
|
||||||
sKeyStr = StrUtil.toCamelCase(sKeyStr);
|
|
||||||
propDesc = targetPropDescMap.get(sKeyStr);
|
|
||||||
return propDesc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
|
|||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public <T> T toBean(final T bean) {
|
public <T> T toBean(final T bean) {
|
||||||
return toBean(bean, false);
|
return BeanUtil.fillBeanWithMap(this, bean, CopyOptions.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -206,33 +206,7 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
|
|||||||
* @since 3.3.1
|
* @since 3.3.1
|
||||||
*/
|
*/
|
||||||
public <T> T toBeanIgnoreCase(final T bean) {
|
public <T> T toBeanIgnoreCase(final T bean) {
|
||||||
BeanUtil.fillBeanWithMapIgnoreCase(this, bean, false);
|
return BeanUtil.fillBeanWithMap(this, bean, CopyOptions.of().setIgnoreCase(true));
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为Bean对象
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param bean Bean
|
|
||||||
* @param isToCamelCase 是否转换为驼峰模式
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public <T> T toBean(final T bean, final boolean isToCamelCase) {
|
|
||||||
BeanUtil.fillBeanWithMap(this, bean, isToCamelCase, false);
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为Bean对象,并使用驼峰法模式转换
|
|
||||||
*
|
|
||||||
* @param <T> Bean类型
|
|
||||||
* @param bean Bean
|
|
||||||
* @return Bean
|
|
||||||
*/
|
|
||||||
public <T> T toBeanWithCamelCase(final T bean) {
|
|
||||||
BeanUtil.fillBeanWithMap(this, bean, true, false);
|
|
||||||
return bean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +90,8 @@ public class BeanUtilTest {
|
|||||||
.put("aGe", 12)
|
.put("aGe", 12)
|
||||||
.put("openId", "DFDFSDFWERWER")
|
.put("openId", "DFDFSDFWERWER")
|
||||||
.build();
|
.build();
|
||||||
final SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
|
final SubPerson person = BeanUtil.fillBeanWithMap(
|
||||||
|
map, new SubPerson(), CopyOptions.of().setIgnoreCase(true));
|
||||||
Assertions.assertEquals("Joe", person.getName());
|
Assertions.assertEquals("Joe", person.getName());
|
||||||
Assertions.assertEquals(12, person.getAge());
|
Assertions.assertEquals(12, person.getAge());
|
||||||
Assertions.assertEquals("DFDFSDFWERWER", person.getOpenid());
|
Assertions.assertEquals("DFDFSDFWERWER", person.getOpenid());
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.core.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Issue3452Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fillBeanWithMapTest() {
|
||||||
|
final Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put("name", "JohnDoe");
|
||||||
|
properties.put("user_age", 25);
|
||||||
|
final User user = BeanUtil.fillBeanWithMap(
|
||||||
|
properties, new User(), CopyOptions.of());
|
||||||
|
Assertions.assertEquals("JohnDoe", user.getName());
|
||||||
|
Assertions.assertEquals(25, user.getUserAge());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class User {
|
||||||
|
private String name;
|
||||||
|
private int userAge;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ package org.dromara.hutool.core.xml;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.dromara.hutool.core.bean.BeanUtil;
|
import org.dromara.hutool.core.bean.BeanUtil;
|
||||||
|
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||||
import org.dromara.hutool.core.collection.ListUtil;
|
import org.dromara.hutool.core.collection.ListUtil;
|
||||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||||
import org.dromara.hutool.core.io.file.FileUtil;
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
@ -282,7 +283,7 @@ public class XmlUtilTest {
|
|||||||
// 标准方式
|
// 标准方式
|
||||||
final Map<String, Object> map = XmlUtil.xmlToMap(doc.getFirstChild());
|
final Map<String, Object> map = XmlUtil.xmlToMap(doc.getFirstChild());
|
||||||
final SmsRes res = new SmsRes();
|
final SmsRes res = new SmsRes();
|
||||||
BeanUtil.fillBeanWithMap(map, res, true);
|
BeanUtil.fillBeanWithMap(map, res, CopyOptions.of().setIgnoreError(true));
|
||||||
|
|
||||||
// toBean方式
|
// toBean方式
|
||||||
final SmsRes res1 = XmlUtil.xmlToBean(doc.getFirstChild(), SmsRes.class);
|
final SmsRes res1 = XmlUtil.xmlToBean(doc.getFirstChild(), SmsRes.class);
|
||||||
|
@ -86,6 +86,13 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 仅用于测试 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.hutool</groupId>
|
||||||
|
<artifactId>hutool-json</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.brotli</groupId>
|
<groupId>org.brotli</groupId>
|
||||||
<artifactId>dec</artifactId>
|
<artifactId>dec</artifactId>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user