mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
add xxxCopier
This commit is contained in:
parent
858a0826f0
commit
b848efb7b4
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,7 +2,12 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.23 (2022-03-19)
|
||||
# 5.8.0 (2022-03-19)
|
||||
|
||||
### ❌不兼容特性
|
||||
* 【db 】 【不向下兼容】增加MongoDB4.x支持(pr#568@Gitee)
|
||||
* 【json 】 【可能兼容问题】修改JSONObject结构,继承自MapWrapper
|
||||
* 【core 】 【可能兼容问题】BeanCopier重构,新建XXXCopier,删除XXXValueProvider
|
||||
|
||||
### 🐣新特性
|
||||
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
|
||||
@ -14,7 +19,6 @@
|
||||
* 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee)
|
||||
* 【core 】 ArrayUtil增加replace方法(pr#570@Gitee)
|
||||
* 【core 】 CsvReadConfig增加自定义标题行行号(issue#2180@Github)
|
||||
* 【db 】 增加MongoDB4.x支持(pr#568@Gitee)
|
||||
* 【core 】 FileAppender优化初始List大小(pr#2197@Github)
|
||||
* 【core 】 Base32增加pad支持(pr#2195@Github)
|
||||
* 【core 】 Dict增加setFields方法(pr#578@Gitee)
|
||||
@ -22,7 +26,8 @@
|
||||
* 【db 】 Oracle中Column#typeName后的长度去掉(pr#563@Gitee)
|
||||
* 【poi 】 优化ExcelReader,采用只读模式(pr#2204@Gitee)
|
||||
* 【poi 】 优化ExcelBase,将alias放入
|
||||
*
|
||||
* 【poi 】 优化ExcelBase,将alias放入
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee)
|
||||
* 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee)
|
||||
@ -31,6 +36,8 @@
|
||||
* 【http 】 修复GET重定向时,携带参数问题(issue#2189@Github)
|
||||
* 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题(pr#2188@Github)
|
||||
* 【core 】 修复CopyOptions中fieldNameEditor无效问题(issue#2202@Github)
|
||||
* 【json 】 修复JSON对Map.Entry的解析问题
|
||||
* 【core 】 修复MapConverter中map与map转换兼容问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.22 (2022-03-01)
|
||||
|
28
hutool-core/src/main/java/cn/hutool/core/bean/copier/AbsCopier.java
Executable file
28
hutool-core/src/main/java/cn/hutool/core/bean/copier/AbsCopier.java
Executable file
@ -0,0 +1,28 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.lang.copier.Copier;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
/**
|
||||
* 抽象的对象拷贝封装,提供来源对象、目标对象持有
|
||||
*
|
||||
* @param <S> 来源对象类型
|
||||
* @param <T> 目标对象类型
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public abstract class AbsCopier<S, T> implements Copier<T> {
|
||||
|
||||
protected final S source;
|
||||
protected final T target;
|
||||
/**
|
||||
* 拷贝选项
|
||||
*/
|
||||
protected final CopyOptions copyOptions;
|
||||
|
||||
public AbsCopier(S source, T target, CopyOptions copyOptions) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.copyOptions = ObjectUtil.defaultIfNull(copyOptions, CopyOptions::create);
|
||||
}
|
||||
}
|
@ -1,19 +1,9 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.bean.BeanException;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.DynaBean;
|
||||
import cn.hutool.core.bean.copier.provider.BeanValueProvider;
|
||||
import cn.hutool.core.bean.copier.provider.DynaBeanValueProvider;
|
||||
import cn.hutool.core.bean.copier.provider.MapValueProvider;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.copier.Copier;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -34,27 +24,19 @@ import java.util.Map;
|
||||
public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 源对象 */
|
||||
@SuppressWarnings("NonSerializableFieldInSerializableClass")
|
||||
private final Object source;
|
||||
/** 目标对象 */
|
||||
private final T dest;
|
||||
/** 目标的类型(用于泛型类注入) */
|
||||
private final Type destType;
|
||||
/** 拷贝选项 */
|
||||
private final CopyOptions copyOptions;
|
||||
private final Copier<T> copier;
|
||||
|
||||
/**
|
||||
* 创建BeanCopier
|
||||
*
|
||||
* @param <T> 目标Bean类型
|
||||
* @param source 来源对象,可以是Bean或者Map
|
||||
* @param dest 目标Bean对象
|
||||
* @param target 目标Bean对象
|
||||
* @param copyOptions 拷贝属性选项
|
||||
* @return BeanCopier
|
||||
*/
|
||||
public static <T> BeanCopier<T> create(Object source, T dest, CopyOptions copyOptions) {
|
||||
return create(source, dest, dest.getClass(), copyOptions);
|
||||
public static <T> BeanCopier<T> create(Object source, T target, CopyOptions copyOptions) {
|
||||
return create(source, target, target.getClass(), copyOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,253 +44,48 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
*
|
||||
* @param <T> 目标Bean类型
|
||||
* @param source 来源对象,可以是Bean或者Map
|
||||
* @param dest 目标Bean对象
|
||||
* @param target 目标Bean对象
|
||||
* @param destType 目标的泛型类型,用于标注有泛型参数的Bean对象
|
||||
* @param copyOptions 拷贝属性选项
|
||||
* @return BeanCopier
|
||||
*/
|
||||
public static <T> BeanCopier<T> create(Object source, T dest, Type destType, CopyOptions copyOptions) {
|
||||
return new BeanCopier<>(source, dest, destType, copyOptions);
|
||||
public static <T> BeanCopier<T> create(Object source, T target, Type destType, CopyOptions copyOptions) {
|
||||
return new BeanCopier<>(source, target, destType, copyOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源对象,可以是Bean或者Map
|
||||
* @param dest 目标Bean对象
|
||||
* @param destType 目标的泛型类型,用于标注有泛型参数的Bean对象
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标的泛型类型,用于标注有泛型参数的Bean对象
|
||||
* @param copyOptions 拷贝属性选项
|
||||
*/
|
||||
public BeanCopier(Object source, T dest, Type destType, CopyOptions copyOptions) {
|
||||
this.source = source;
|
||||
this.dest = dest;
|
||||
this.destType = destType;
|
||||
this.copyOptions = copyOptions;
|
||||
public BeanCopier(Object source, T target, Type targetType, CopyOptions copyOptions) {
|
||||
Copier<T> copier;
|
||||
if (source instanceof Map) {
|
||||
if (target instanceof Map) {
|
||||
//noinspection unchecked
|
||||
copier = (Copier<T>) new MapToMapCopier((Map<?, ?>) source, (Map<?, ?>) target, targetType, copyOptions);
|
||||
} else {
|
||||
copier = new MapToBeanCopier<>((Map<?, ?>) source, target, targetType, copyOptions);
|
||||
}
|
||||
}else if(source instanceof ValueProvider){
|
||||
//noinspection unchecked
|
||||
copier = new ValueProviderToBeanCopier<>((ValueProvider<String>) source, target, targetType, copyOptions);
|
||||
} else {
|
||||
if (target instanceof Map) {
|
||||
//noinspection unchecked
|
||||
copier = (Copier<T>) new BeanToMapCopier(source, (Map<?, ?>) target, targetType, copyOptions);
|
||||
} else {
|
||||
copier = new BeanToBeanCopier<>(source, target, targetType, copyOptions);
|
||||
}
|
||||
}
|
||||
this.copier = copier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T copy() {
|
||||
if (null != this.source) {
|
||||
if (this.source instanceof ValueProvider) {
|
||||
// 目标只支持Bean
|
||||
valueProviderToBean((ValueProvider<String>) this.source, this.dest);
|
||||
} else if (this.source instanceof DynaBean) {
|
||||
// 目标只支持Bean
|
||||
valueProviderToBean(new DynaBeanValueProvider((DynaBean) this.source, copyOptions.ignoreError), this.dest);
|
||||
} else if (this.source instanceof Map) {
|
||||
if (this.dest instanceof Map) {
|
||||
mapToMap((Map<?, ?>) this.source, (Map<?, ?>) this.dest);
|
||||
} else {
|
||||
mapToBean((Map<?, ?>) this.source, this.dest);
|
||||
}
|
||||
} else {
|
||||
if (this.dest instanceof Map) {
|
||||
beanToMap(this.source, (Map<?, ?>) this.dest);
|
||||
} else {
|
||||
beanToBean(this.source, this.dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean和Bean之间属性拷贝
|
||||
*
|
||||
* @param providerBean 来源Bean
|
||||
* @param destBean 目标Bean
|
||||
*/
|
||||
private void beanToBean(Object providerBean, Object destBean) {
|
||||
valueProviderToBean(
|
||||
new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor), destBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map转Bean属性拷贝
|
||||
*
|
||||
* @param map Map
|
||||
* @param bean Bean
|
||||
*/
|
||||
private void mapToBean(Map<?, ?> map, Object bean) {
|
||||
valueProviderToBean(
|
||||
new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor),
|
||||
bean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map转Map
|
||||
*
|
||||
* @param source 源Map
|
||||
* @param targetMap 目标Map
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void mapToMap(Map source, Map targetMap) {
|
||||
source.forEach((key, value)->{
|
||||
final CopyOptions copyOptions = this.copyOptions;
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||
return;
|
||||
}
|
||||
|
||||
if(key instanceof CharSequence){
|
||||
if (CollUtil.contains(ignoreSet, key)) {
|
||||
// 目标属性值被忽略或值提供者无此key时跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 对源key做映射,映射后为null的忽略之
|
||||
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
|
||||
if(null == key){
|
||||
return;
|
||||
}
|
||||
|
||||
value = copyOptions.editFieldValue(key.toString(), value);
|
||||
}
|
||||
|
||||
|
||||
if ((null == value && copyOptions.ignoreNullValue) || source == value) {
|
||||
// 当允许跳过空时,跳过
|
||||
//值不能为bean本身,防止循环引用,此类也跳过
|
||||
return;
|
||||
}
|
||||
|
||||
targetMap.put(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转Map
|
||||
*
|
||||
* @param bean bean对象
|
||||
* @param targetMap 目标的Map
|
||||
* @since 4.1.22
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void beanToMap(Object bean, Map targetMap) {
|
||||
final CopyOptions copyOptions = this.copyOptions;
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
|
||||
BeanUtil.descForEach(bean.getClass(), (prop)->{
|
||||
if(false == prop.isReadable(copyOptions.transientSupport)){
|
||||
// 忽略的属性跳过之
|
||||
return;
|
||||
}
|
||||
String key = prop.getFieldName();
|
||||
if (CollUtil.contains(ignoreSet, key)) {
|
||||
// 目标属性值被忽略或值提供者无此key时跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 对key做映射,映射后为null的忽略之
|
||||
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key, false));
|
||||
if(null == key){
|
||||
return;
|
||||
}
|
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||
return;
|
||||
}
|
||||
|
||||
Object value;
|
||||
try {
|
||||
value = prop.getValue(bean);
|
||||
} catch (Exception e) {
|
||||
if (copyOptions.ignoreError) {
|
||||
return;// 忽略反射失败
|
||||
} else {
|
||||
throw new BeanException(e, "Get value of [{}] error!", prop.getFieldName());
|
||||
}
|
||||
}
|
||||
if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// since 5.7.15
|
||||
value = copyOptions.editFieldValue(key, value);
|
||||
|
||||
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
|
||||
// 当允许跳过空时,跳过
|
||||
//值不能为bean本身,防止循环引用,此类也跳过
|
||||
return;
|
||||
}
|
||||
|
||||
targetMap.put(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 值提供器转Bean<br>
|
||||
* 此方法通过遍历目标Bean的字段,从ValueProvider查找对应值
|
||||
*
|
||||
* @param valueProvider 值提供器
|
||||
* @param bean Bean
|
||||
*/
|
||||
private void valueProviderToBean(ValueProvider<String> valueProvider, Object bean) {
|
||||
if (null == valueProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CopyOptions copyOptions = this.copyOptions;
|
||||
Class<?> actualEditable = bean.getClass();
|
||||
if (null != copyOptions.editable) {
|
||||
// 检查限制类是否为target的父类或接口
|
||||
if (false == copyOptions.editable.isInstance(bean)) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Target class [{}] not assignable to Editable class [{}]", bean.getClass().getName(), copyOptions.editable.getName()));
|
||||
}
|
||||
actualEditable = copyOptions.editable;
|
||||
}
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
|
||||
// 遍历目标bean的所有属性
|
||||
BeanUtil.descForEach(actualEditable, (prop)->{
|
||||
if(false == prop.isWritable(this.copyOptions.transientSupport)){
|
||||
// 字段不可写,跳过之
|
||||
return;
|
||||
}
|
||||
// 检查属性名
|
||||
final String fieldName = prop.getFieldName();
|
||||
if (CollUtil.contains(ignoreSet, fieldName)) {
|
||||
// 目标属性值被忽略或值提供者无此key时跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 对key做映射,映射后为null的忽略之
|
||||
final String sourceKey = copyOptions.getMappedFieldName(fieldName, true);
|
||||
if(null == sourceKey){
|
||||
return;
|
||||
}
|
||||
if (false == valueProvider.containsKey(sourceKey)) {
|
||||
// 无对应值可提供
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标字段真实类型
|
||||
final Type fieldType = TypeUtil.getActualType(this.destType ,prop.getFieldType());
|
||||
|
||||
// 获取属性值
|
||||
Object value = valueProvider.value(sourceKey, fieldType);
|
||||
if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// since 5.7.15
|
||||
value = copyOptions.editFieldValue(sourceKey, value);
|
||||
|
||||
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
|
||||
// 当允许跳过空时,跳过
|
||||
// 值不能为bean本身,防止循环引用
|
||||
return;
|
||||
}
|
||||
|
||||
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||
});
|
||||
return copier.copy();
|
||||
}
|
||||
}
|
||||
|
86
hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanToBeanCopier.java
Executable file
86
hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanToBeanCopier.java
Executable file
@ -0,0 +1,86 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.PropDesc;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean属性拷贝到Bean中的拷贝器
|
||||
*
|
||||
* @param <S> 源Bean类型
|
||||
* @param <T> 目标Bean类型
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class BeanToBeanCopier<S, T> extends AbsCopier<S, T> {
|
||||
|
||||
/**
|
||||
* 目标的类型(用于泛型类注入)
|
||||
*/
|
||||
private final Type targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源Map
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标泛型类型
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public BeanToBeanCopier(S source, T target, Type targetType, CopyOptions copyOptions) {
|
||||
super(source, target, copyOptions);
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T copy() {
|
||||
Class<?> actualEditable = target.getClass();
|
||||
if (null != copyOptions.editable) {
|
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target),
|
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName());
|
||||
actualEditable = copyOptions.editable;
|
||||
}
|
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase);
|
||||
|
||||
final Map<String, PropDesc> sourcePropDescMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(copyOptions.ignoreCase);
|
||||
sourcePropDescMap.forEach((sFieldName, sDesc) -> {
|
||||
if (null == sFieldName || false == sDesc.isReadable(copyOptions.transientSupport)) {
|
||||
// 字段空或不可读,跳过
|
||||
return;
|
||||
}
|
||||
|
||||
sFieldName = copyOptions.editFieldName(sFieldName);
|
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sFieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查目标字段可写性
|
||||
final PropDesc tDesc = targetPropDescMap.get(sFieldName);
|
||||
if (null == tDesc || false == tDesc.isWritable(this.copyOptions.transientSupport)) {
|
||||
// 字段不可写,跳过之
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查源对象属性是否过滤属性
|
||||
Object sValue = sDesc.getValue(this.source);
|
||||
if (false == copyOptions.testPropertyFilter(sDesc.getField(), sValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标字段真实类型并转换源值
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType, tDesc.getFieldType());
|
||||
sValue = Convert.convertWithCheck(fieldType, sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = copyOptions.editFieldValue(sFieldName, sValue);
|
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, sValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||
});
|
||||
return this.target;
|
||||
}
|
||||
}
|
82
hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanToMapCopier.java
Executable file
82
hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanToMapCopier.java
Executable file
@ -0,0 +1,82 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.PropDesc;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean属性拷贝到Map中的拷贝器
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class BeanToMapCopier extends AbsCopier<Object, Map> {
|
||||
|
||||
/**
|
||||
* 目标的Map类型(用于泛型类注入)
|
||||
*/
|
||||
private final Type targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源Map
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标泛型类型
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public BeanToMapCopier(Object source, Map target, Type targetType, CopyOptions copyOptions) {
|
||||
super(source, target, copyOptions);
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map copy() {
|
||||
Class<?> actualEditable = source.getClass();
|
||||
if (null != copyOptions.editable) {
|
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(source),
|
||||
"Source class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName());
|
||||
actualEditable = copyOptions.editable;
|
||||
}
|
||||
|
||||
final Map<String, PropDesc> sourcePropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase);
|
||||
sourcePropDescMap.forEach((sFieldName, sDesc) -> {
|
||||
if (null == sFieldName || false == sDesc.isReadable(copyOptions.transientSupport)) {
|
||||
// 字段空或不可读,跳过
|
||||
return;
|
||||
}
|
||||
|
||||
sFieldName = copyOptions.editFieldName(sFieldName);
|
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sFieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查源对象属性是否过滤属性
|
||||
Object sValue = sDesc.getValue(this.source);
|
||||
if (false == copyOptions.testPropertyFilter(sDesc.getField(), sValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标值真实类型并转换源值
|
||||
final Type[] typeArguments = TypeUtil.getTypeArguments(this.targetType);
|
||||
if(null != typeArguments){
|
||||
sValue = Convert.convertWithCheck(typeArguments[1], sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = copyOptions.editFieldValue(sFieldName, sValue);
|
||||
}
|
||||
|
||||
// 目标赋值
|
||||
if(null != sValue || false == copyOptions.ignoreNullValue){
|
||||
//noinspection unchecked
|
||||
target.put(sFieldName, sValue);
|
||||
}
|
||||
});
|
||||
return this.target;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.map.BiMap;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
@ -22,7 +22,8 @@ public class CopyOptions implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类
|
||||
* 限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类<br>
|
||||
* 如果目标对象是Map,源对象是Bean,则作用于源对象上
|
||||
*/
|
||||
protected Class<?> editable;
|
||||
/**
|
||||
@ -30,13 +31,10 @@ public class CopyOptions implements Serializable {
|
||||
*/
|
||||
protected boolean ignoreNullValue;
|
||||
/**
|
||||
* 属性过滤器,断言通过的属性才会被复制
|
||||
* 属性过滤器,断言通过的属性才会被复制<br>
|
||||
* 断言参数中Field为源对象的字段对象,如果源对象为Map,使用目标对象,Object为源对象的对应值
|
||||
*/
|
||||
protected BiPredicate<Field, Object> propertiesFilter;
|
||||
/**
|
||||
* 忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值
|
||||
*/
|
||||
protected String[] ignoreProperties;
|
||||
private BiPredicate<Field, Object> propertiesFilter;
|
||||
/**
|
||||
* 是否忽略字段注入错误
|
||||
*/
|
||||
@ -46,13 +44,10 @@ public class CopyOptions implements Serializable {
|
||||
*/
|
||||
protected boolean ignoreCase;
|
||||
/**
|
||||
* 拷贝属性的字段映射,用于不同的属性之前拷贝做对应表用
|
||||
* 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等<br>
|
||||
* 规则为,{@link Editor#edit(Object)}属性为源对象的字段名称或key,返回值为目标对象的字段名称或key
|
||||
*/
|
||||
protected BiMap<String, String> fieldMapping;
|
||||
/**
|
||||
* 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等
|
||||
*/
|
||||
protected Editor<String> fieldNameEditor;
|
||||
private Editor<String> fieldNameEditor;
|
||||
/**
|
||||
* 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
|
||||
*/
|
||||
@ -66,6 +61,7 @@ public class CopyOptions implements Serializable {
|
||||
*/
|
||||
protected boolean override = true;
|
||||
|
||||
//region create
|
||||
/**
|
||||
* 创建拷贝选项
|
||||
*
|
||||
@ -86,6 +82,7 @@ public class CopyOptions implements Serializable {
|
||||
public static CopyOptions create(Class<?> editable, boolean ignoreNullValue, String... ignoreProperties) {
|
||||
return new CopyOptions(editable, ignoreNullValue, ignoreProperties);
|
||||
}
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* 构造拷贝选项
|
||||
@ -104,7 +101,7 @@ public class CopyOptions implements Serializable {
|
||||
this.propertiesFilter = (f, v) -> true;
|
||||
this.editable = editable;
|
||||
this.ignoreNullValue = ignoreNullValue;
|
||||
this.ignoreProperties = ignoreProperties;
|
||||
this.setIgnoreProperties(ignoreProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,7 +137,8 @@ public class CopyOptions implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性过滤器,断言通过的属性才会被复制
|
||||
* 属性过滤器,断言通过的属性才会被复制<br>
|
||||
* {@link BiPredicate#test(Object, Object)}返回{@code true}则属性通过,{@code false}不通过,抛弃之
|
||||
*
|
||||
* @param propertiesFilter 属性过滤器
|
||||
* @return CopyOptions
|
||||
@ -157,8 +155,7 @@ public class CopyOptions implements Serializable {
|
||||
* @return CopyOptions
|
||||
*/
|
||||
public CopyOptions setIgnoreProperties(String... ignoreProperties) {
|
||||
this.ignoreProperties = ignoreProperties;
|
||||
return this;
|
||||
return setPropertiesFilter((field, o) -> false == ArrayUtil.contains(ignoreProperties, field.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,8 +207,7 @@ public class CopyOptions implements Serializable {
|
||||
* @return CopyOptions
|
||||
*/
|
||||
public CopyOptions setFieldMapping(Map<String, String> fieldMapping) {
|
||||
this.fieldMapping = new BiMap<>(fieldMapping);
|
||||
return this;
|
||||
return setFieldNameEditor((key-> fieldMapping.getOrDefault(key, key)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -253,18 +249,6 @@ public class CopyOptions implements Serializable {
|
||||
this.fieldValueEditor.apply(fieldName, fieldValue) : fieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
||||
*
|
||||
* @return 是否支持
|
||||
* @since 5.4.2
|
||||
* @deprecated 无需此方法,内部使用直接调用属性
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isTransientSupport() {
|
||||
return this.transientSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
||||
*
|
||||
@ -289,25 +273,6 @@ public class CopyOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得映射后的字段名<br>
|
||||
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param reversed 是否反向映射
|
||||
* @return 映射后的字段名
|
||||
*/
|
||||
protected String getMappedFieldName(String fieldName, boolean reversed) {
|
||||
final BiMap<String, String> fieldMapping = this.fieldMapping;
|
||||
if(null != fieldMapping){
|
||||
final String mappingName = reversed ? fieldMapping.getKey(fieldName) : fieldMapping.get(fieldName);
|
||||
if(null != mappingName){
|
||||
return mappingName;
|
||||
}
|
||||
}
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换字段名为编辑后的字段名
|
||||
*
|
||||
@ -318,4 +283,15 @@ public class CopyOptions implements Serializable {
|
||||
protected String editFieldName(String fieldName) {
|
||||
return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试是否保留字段,{@code true}保留,{@code false}不保留
|
||||
*
|
||||
* @param field 字段
|
||||
* @param value 值
|
||||
* @return 是否保留
|
||||
*/
|
||||
protected boolean testPropertyFilter(Field field, Object value) {
|
||||
return null == this.propertiesFilter || this.propertiesFilter.test(field, value);
|
||||
}
|
||||
}
|
||||
|
125
hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToBeanCopier.java
Executable file
125
hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToBeanCopier.java
Executable file
@ -0,0 +1,125 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.PropDesc;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.map.MapWrapper;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map属性拷贝到Bean中的拷贝器
|
||||
*
|
||||
* @param <T> 目标Bean类型
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
|
||||
|
||||
/**
|
||||
* 目标的类型(用于泛型类注入)
|
||||
*/
|
||||
private final Type targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源Map
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标泛型类型
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public MapToBeanCopier(Map<?, ?> source, T target, Type targetType, CopyOptions copyOptions) {
|
||||
super(source, target, copyOptions);
|
||||
|
||||
// 针对MapWrapper特殊处理,提供的Map包装了忽略大小写的Map,则默认转Bean的时候也忽略大小写,如JSONObject
|
||||
if(source instanceof MapWrapper){
|
||||
final Map<?, ?> raw = ((MapWrapper<?, ?>) source).getRaw();
|
||||
if(raw instanceof CaseInsensitiveMap){
|
||||
copyOptions.setIgnoreCase(true);
|
||||
}
|
||||
}
|
||||
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T copy() {
|
||||
Class<?> actualEditable = target.getClass();
|
||||
if (null != copyOptions.editable) {
|
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target),
|
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName());
|
||||
actualEditable = copyOptions.editable;
|
||||
}
|
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase);
|
||||
|
||||
this.source.forEach((sKey, sValue) -> {
|
||||
if (null == sKey) {
|
||||
return;
|
||||
}
|
||||
String sKeyStr = copyOptions.editFieldName(sKey.toString());
|
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sKeyStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查目标字段可写性
|
||||
PropDesc tDesc = findPropDesc(targetPropDescMap, sKeyStr);
|
||||
if (null == tDesc || false == tDesc.isWritable(this.copyOptions.transientSupport)) {
|
||||
// 字段不可写,跳过之
|
||||
return;
|
||||
}
|
||||
sKeyStr = tDesc.getFieldName();
|
||||
|
||||
// 检查目标是否过滤属性
|
||||
if (false == copyOptions.testPropertyFilter(tDesc.getField(), sValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标字段真实类型并转换源值
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType, tDesc.getFieldType());
|
||||
Object newValue = Convert.convertWithCheck(fieldType, sValue, null, this.copyOptions.ignoreError);
|
||||
newValue = copyOptions.editFieldValue(sKeyStr, newValue);
|
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, newValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||
});
|
||||
return this.target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找Map对应Bean的名称<br>
|
||||
* 尝试原名称、转驼峰名称、isXxx去掉is的名称
|
||||
*
|
||||
* @param targetPropDescMap 目标bean的属性描述Map
|
||||
* @param sKeyStr 键或字段名
|
||||
* @return {@link PropDesc}
|
||||
*/
|
||||
private PropDesc findPropDesc(Map<String, PropDesc> targetPropDescMap, String sKeyStr){
|
||||
PropDesc propDesc = targetPropDescMap.get(sKeyStr);
|
||||
if(null != propDesc){
|
||||
return propDesc;
|
||||
}
|
||||
|
||||
// 转驼峰尝试查找
|
||||
sKeyStr = StrUtil.toCamelCase(sKeyStr);
|
||||
propDesc = targetPropDescMap.get(sKeyStr);
|
||||
if(null != propDesc){
|
||||
return propDesc;
|
||||
}
|
||||
|
||||
// boolean类型参数名转换尝试查找
|
||||
if(sKeyStr.startsWith("is")){
|
||||
sKeyStr = StrUtil.removePreAndLowerFirst(sKeyStr, 2);
|
||||
propDesc = targetPropDescMap.get(sKeyStr);
|
||||
return propDesc;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
65
hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java
Executable file
65
hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java
Executable file
@ -0,0 +1,65 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map属性拷贝到Map中的拷贝器
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class MapToMapCopier extends AbsCopier<Map, Map> {
|
||||
|
||||
/**
|
||||
* 目标的类型(用于泛型类注入)
|
||||
*/
|
||||
private final Type targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源Map
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标泛型类型
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public MapToMapCopier(Map source, Map target, Type targetType, CopyOptions copyOptions) {
|
||||
super(source, target, copyOptions);
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map copy() {
|
||||
this.source.forEach((sKey, sValue) -> {
|
||||
if (null == sKey) {
|
||||
return;
|
||||
}
|
||||
final String sKeyStr = copyOptions.editFieldName(sKey.toString());
|
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sKeyStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Object targetValue = target.get(sKeyStr);
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if (false == copyOptions.override && null != targetValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标值真实类型并转换源值
|
||||
final Type[] typeArguments = TypeUtil.getTypeArguments(this.targetType);
|
||||
if(null != typeArguments){
|
||||
sValue = Convert.convertWithCheck(typeArguments[1], sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = copyOptions.editFieldValue(sKeyStr, sValue);
|
||||
}
|
||||
|
||||
// 目标赋值
|
||||
target.put(sKeyStr, sValue);
|
||||
});
|
||||
return this.target;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.PropDesc;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link ValueProvider}属性拷贝到Bean中的拷贝器
|
||||
*
|
||||
* @param <T> 目标Bean类型
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class ValueProviderToBeanCopier<T> extends AbsCopier<ValueProvider<String>, T> {
|
||||
|
||||
/**
|
||||
* 目标的类型(用于泛型类注入)
|
||||
*/
|
||||
private final Type targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 来源Map
|
||||
* @param target 目标Bean对象
|
||||
* @param targetType 目标泛型类型
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public ValueProviderToBeanCopier(ValueProvider<String> source, T target, Type targetType, CopyOptions copyOptions) {
|
||||
super(source, target, copyOptions);
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T copy() {
|
||||
Class<?> actualEditable = target.getClass();
|
||||
if (null != copyOptions.editable) {
|
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target),
|
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName());
|
||||
actualEditable = copyOptions.editable;
|
||||
}
|
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase);
|
||||
|
||||
targetPropDescMap.forEach((tFieldName, tDesc) -> {
|
||||
if (null == tFieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查目标字段可写性
|
||||
if (null == tDesc || false == tDesc.isWritable(this.copyOptions.transientSupport)) {
|
||||
// 字段不可写,跳过之
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标字段真实类型
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType ,tDesc.getFieldType());
|
||||
|
||||
// 检查目标对象属性是否过滤属性
|
||||
Object sValue = source.value(tFieldName, fieldType);
|
||||
if (false == copyOptions.testPropertyFilter(tDesc.getField(), sValue)) {
|
||||
return;
|
||||
}
|
||||
sValue = copyOptions.editFieldValue(tFieldName, sValue);
|
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, sValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||
});
|
||||
return this.target;
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package cn.hutool.core.bean.copier.provider;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.PropDesc;
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.map.FuncKeyMap;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean的值提供者
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class BeanValueProvider implements ValueProvider<String> {
|
||||
|
||||
private final Object source;
|
||||
private final boolean ignoreError;
|
||||
final Map<String, PropDesc> sourcePdMap;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param bean Bean
|
||||
* @param ignoreCase 是否忽略字段大小写
|
||||
* @param ignoreError 是否忽略字段值读取错误
|
||||
*/
|
||||
public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) {
|
||||
this(bean, ignoreCase, ignoreError, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param bean Bean
|
||||
* @param ignoreCase 是否忽略字段大小写
|
||||
* @param ignoreError 是否忽略字段值读取错误
|
||||
*/
|
||||
public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor<String> keyEditor) {
|
||||
this.source = bean;
|
||||
this.ignoreError = ignoreError;
|
||||
final Map<String, PropDesc> sourcePdMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(ignoreCase);
|
||||
// issue#2202@Github
|
||||
// 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key
|
||||
this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> {
|
||||
if (ignoreCase && key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
if (null != keyEditor) {
|
||||
key = keyEditor.edit(key.toString());
|
||||
}
|
||||
return key.toString();
|
||||
});
|
||||
this.sourcePdMap.putAll(sourcePdMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object value(String key, Type valueType) {
|
||||
final PropDesc sourcePd = getPropDesc(key, valueType);
|
||||
|
||||
Object result = null;
|
||||
if (null != sourcePd) {
|
||||
result = sourcePd.getValue(this.source, valueType, this.ignoreError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(String key) {
|
||||
final PropDesc sourcePd = getPropDesc(key, null);
|
||||
|
||||
// 字段描述不存在或忽略读的情况下,表示不存在
|
||||
return null != sourcePd && sourcePd.isReadable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得属性描述
|
||||
*
|
||||
* @param key 字段名
|
||||
* @param valueType 值类型,用于判断是否为Boolean,可以为null
|
||||
* @return 属性描述
|
||||
*/
|
||||
private PropDesc getPropDesc(String key, Type valueType) {
|
||||
PropDesc sourcePd = sourcePdMap.get(key);
|
||||
if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) {
|
||||
//boolean类型字段字段名支持两种方式
|
||||
sourcePd = sourcePdMap.get(StrUtil.upperFirstAndAddPre(key, "is"));
|
||||
}
|
||||
|
||||
return sourcePd;
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package cn.hutool.core.bean.copier.provider;
|
||||
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.map.FuncKeyMap;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map值提供者,支持驼峰和下划线的key兼容。<br>
|
||||
* 假设目标属性为firstName,则Map中以下形式的值都可以对应:
|
||||
* <ul>
|
||||
* <li>firstName</li>
|
||||
* <li>first_name</li>
|
||||
* <li>isFirstName(如果为Boolean或boolean类型的值)</li>
|
||||
* <li>is_first_name(如果为Boolean或boolean类型的值)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class MapValueProvider implements ValueProvider<String> {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final Map map;
|
||||
private final boolean ignoreError;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param map Map
|
||||
* @param ignoreCase 是否忽略key的大小写
|
||||
*/
|
||||
public MapValueProvider(Map<?, ?> map, boolean ignoreCase) {
|
||||
this(map, ignoreCase, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param map Map
|
||||
* @param ignoreCase 是否忽略key的大小写
|
||||
* @param ignoreError 是否忽略错误
|
||||
* @since 5.3.2
|
||||
*/
|
||||
public MapValueProvider(Map<?, ?> map, boolean ignoreCase, boolean ignoreError) {
|
||||
this(map, ignoreCase, ignoreError, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param map Map
|
||||
* @param ignoreCase 是否忽略key的大小写
|
||||
* @param ignoreError 是否忽略错误
|
||||
* @param keyEditor 自定义键编辑器
|
||||
* @since 5.7.23
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError, Editor<String> keyEditor) {
|
||||
// issue#2202@Github
|
||||
// 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key
|
||||
this.map = new FuncKeyMap(ObjectUtil.clone(map), (key)->{
|
||||
if(ignoreCase && key instanceof CharSequence){
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
if(null != keyEditor){
|
||||
key = keyEditor.edit(key.toString());
|
||||
}
|
||||
return key;
|
||||
});
|
||||
this.map.clear();
|
||||
this.map.putAll(map);
|
||||
|
||||
this.ignoreError = ignoreError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object value(String key, Type valueType) {
|
||||
final String key1 = getKey(key, valueType);
|
||||
if (null == key1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Convert.convertWithCheck(valueType, map.get(key1), null, this.ignoreError);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(String key) {
|
||||
return null != getKey(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得map中可能包含的key,不包含返回null
|
||||
*
|
||||
* @param key map中可能包含的key
|
||||
* @param valueType 值类型,用于判断是否为Boolean,可以为null
|
||||
* @return map中可能包含的key
|
||||
*/
|
||||
private String getKey(String key, Type valueType) {
|
||||
if (map.containsKey(key)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
//检查下划线模式
|
||||
String customKey = StrUtil.toUnderlineCase(key);
|
||||
if (map.containsKey(customKey)) {
|
||||
return customKey;
|
||||
}
|
||||
|
||||
//检查boolean类型
|
||||
if (null == valueType || Boolean.class == valueType || boolean.class == valueType) {
|
||||
//boolean类型字段字段名支持两种方式
|
||||
customKey = StrUtil.upperFirstAndAddPre(key, "is");
|
||||
if (map.containsKey(customKey)) {
|
||||
return customKey;
|
||||
}
|
||||
|
||||
//检查下划线模式
|
||||
customKey = StrUtil.toUnderlineCase(customKey);
|
||||
if (map.containsKey(customKey)) {
|
||||
return customKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,6 @@ import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@ -86,13 +85,11 @@ public class MapConverter extends AbstractConverter<Map<?, ?>> {
|
||||
*/
|
||||
private void convertMapToMap(Map<?, ?> srcMap, Map<Object, Object> targetMap) {
|
||||
final ConverterRegistry convert = ConverterRegistry.getInstance();
|
||||
Object key;
|
||||
Object value;
|
||||
for (Entry<?, ?> entry : srcMap.entrySet()) {
|
||||
key = TypeUtil.isUnknown(this.keyType) ? entry.getKey() : convert.convert(this.keyType, entry.getKey());
|
||||
value = TypeUtil.isUnknown(this.valueType) ? entry.getValue() : convert.convert(this.valueType, entry.getValue());
|
||||
srcMap.forEach((key, value)->{
|
||||
key = TypeUtil.isUnknown(this.keyType) ? key : convert.convert(this.keyType, key);
|
||||
value = TypeUtil.isUnknown(this.valueType) ? value : convert.convert(this.valueType, value);
|
||||
targetMap.put(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1278,7 +1278,7 @@ public class XmlUtil {
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param namespace 命名空间,可以为null
|
||||
* @param ignoreNull 时候忽略值为{@code null}的属性
|
||||
* @param ignoreNull 忽略值为{@code null}的属性
|
||||
* @return XML
|
||||
* @see JAXBUtil#beanToXml(Object)
|
||||
* @since 5.7.10
|
||||
|
@ -73,8 +73,8 @@ public class BeanUtilTest {
|
||||
|
||||
}, CopyOptions.create());
|
||||
|
||||
Assert.assertEquals(person.getName(), "张三");
|
||||
Assert.assertEquals(person.getAge(), 18);
|
||||
Assert.assertEquals("张三", person.getName());
|
||||
Assert.assertEquals(18, person.getAge());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -4,8 +4,25 @@ import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class BeanCopierTest {
|
||||
|
||||
@Test
|
||||
public void beanToMapIgnoreNullTest() {
|
||||
final A a = new A();
|
||||
|
||||
HashMap<Object, Object> map = BeanCopier.create(a, new HashMap<>(), CopyOptions.create()).copy();
|
||||
Assert.assertEquals(1, map.size());
|
||||
Assert.assertTrue(map.containsKey("value"));
|
||||
Assert.assertNull(map.get("value"));
|
||||
|
||||
// 忽略null的情况下,空字段不写入map
|
||||
map = BeanCopier.create(a, new HashMap<>(), CopyOptions.create().ignoreNullValue()).copy();
|
||||
Assert.assertFalse(map.containsKey("value"));
|
||||
Assert.assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试在非覆盖模式下,目标对象有值则不覆盖
|
||||
*/
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.core.collection;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
@ -20,8 +19,6 @@ public class UniqueKeySetTest {
|
||||
|
||||
// 后两个ID重复
|
||||
Assert.assertEquals(2, set.size());
|
||||
|
||||
set.forEach(Console::log);
|
||||
}
|
||||
|
||||
@Data
|
||||
|
Loading…
Reference in New Issue
Block a user