fix bean editor invalid bug

This commit is contained in:
Looly 2022-03-19 14:56:26 +08:00
parent b5361b33fd
commit 2e044cbda1
9 changed files with 111 additions and 54 deletions

View File

@ -30,6 +30,7 @@
* 【core 】 修复NamingCase中大写转换问题pr#572@Gitee * 【core 】 修复NamingCase中大写转换问题pr#572@Gitee
* 【http 】 修复GET重定向时携带参数问题issue#2189@Github * 【http 】 修复GET重定向时携带参数问题issue#2189@Github
* 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题pr#2188@Github * 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题pr#2188@Github
* 【core 】 修复CopyOptions中fieldNameEditor无效问题issue#2202@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.22 (2022-03-01) # 5.7.22 (2022-03-01)

View File

@ -118,7 +118,11 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
* @since 5.4.2 * @since 5.4.2
*/ */
public boolean containsProp(String fieldName) { public boolean containsProp(String fieldName) {
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName); if (Map.class.isAssignableFrom(beanClass)) {
return ((Map<?, ?>) bean).containsKey(fieldName);
} else{
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
}
} }
/** /**

View File

@ -121,7 +121,8 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
* @param destBean 目标Bean * @param destBean 目标Bean
*/ */
private void beanToBean(Object providerBean, Object destBean) { private void beanToBean(Object providerBean, Object destBean) {
valueProviderToBean(new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), destBean); valueProviderToBean(
new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor), destBean);
} }
/** /**
@ -132,7 +133,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
*/ */
private void mapToBean(Map<?, ?> map, Object bean) { private void mapToBean(Map<?, ?> map, Object bean) {
valueProviderToBean( valueProviderToBean(
new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor),
bean bean
); );
} }
@ -161,7 +162,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return; return;
} }
// key做映射映射后为null的忽略之 // key做映射映射后为null的忽略之
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false)); key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
if(null == key){ if(null == key){
return; return;
@ -280,12 +281,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
} }
// 对key做映射映射后为null的忽略之 // 对key做映射映射后为null的忽略之
// 这里 copyOptions.editFieldName() 不能少否则导致 CopyOptions setFieldNameEditor 失效 final String sourceKey = copyOptions.getMappedFieldName(fieldName, true);
final String providerKey = copyOptions.editFieldName(copyOptions.getMappedFieldName(fieldName, true)); if(null == sourceKey){
if(null == providerKey){
return; return;
} }
if (false == valueProvider.containsKey(providerKey)) { if (false == valueProvider.containsKey(sourceKey)) {
// 无对应值可提供 // 无对应值可提供
return; return;
} }
@ -294,13 +294,13 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
final Type fieldType = TypeUtil.getActualType(this.destType ,prop.getFieldType()); final Type fieldType = TypeUtil.getActualType(this.destType ,prop.getFieldType());
// 获取属性值 // 获取属性值
Object value = valueProvider.value(providerKey, fieldType); Object value = valueProvider.value(sourceKey, fieldType);
if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) { if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) {
return; return;
} }
// since 5.7.15 // since 5.7.15
value = copyOptions.editFieldValue(providerKey, value); value = copyOptions.editFieldValue(sourceKey, value);
if ((null == value && copyOptions.ignoreNullValue) || bean == value) { if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
// 当允许跳过空时跳过 // 当允许跳过空时跳过

View File

@ -1,8 +1,7 @@
package cn.hutool.core.bean.copier; package cn.hutool.core.bean.copier;
import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Editor;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.BiMap;
import cn.hutool.core.util.ObjectUtil;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -49,11 +48,7 @@ public class CopyOptions implements Serializable {
/** /**
* 拷贝属性的字段映射用于不同的属性之前拷贝做对应表用 * 拷贝属性的字段映射用于不同的属性之前拷贝做对应表用
*/ */
protected Map<String, String> fieldMapping; protected BiMap<String, String> fieldMapping;
/**
* 反向映射表自动生成用于反向查找
*/
private Map<String, String> reversedFieldMapping;
/** /**
* 字段属性编辑器用于自定义属性转换规则例如驼峰转下划线等 * 字段属性编辑器用于自定义属性转换规则例如驼峰转下划线等
*/ */
@ -215,7 +210,7 @@ public class CopyOptions implements Serializable {
* @return CopyOptions * @return CopyOptions
*/ */
public CopyOptions setFieldMapping(Map<String, String> fieldMapping) { public CopyOptions setFieldMapping(Map<String, String> fieldMapping) {
this.fieldMapping = fieldMapping; this.fieldMapping = new BiMap<>(fieldMapping);
return this; return this;
} }
@ -303,11 +298,14 @@ public class CopyOptions implements Serializable {
* @return 映射后的字段名 * @return 映射后的字段名
*/ */
protected String getMappedFieldName(String fieldName, boolean reversed) { protected String getMappedFieldName(String fieldName, boolean reversed) {
Map<String, String> mapping = reversed ? getReversedMapping() : this.fieldMapping; final BiMap<String, String> fieldMapping = this.fieldMapping;
if (MapUtil.isEmpty(mapping)) { if(null != fieldMapping){
return fieldName; final String mappingName = reversed ? fieldMapping.getKey(fieldName) : fieldMapping.get(fieldName);
if(null != mappingName){
return mappingName;
}
} }
return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName); return fieldName;
} }
/** /**
@ -320,20 +318,4 @@ public class CopyOptions implements Serializable {
protected String editFieldName(String fieldName) { protected String editFieldName(String fieldName) {
return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName; return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName;
} }
/**
* 获取反转之后的映射
*
* @return 反转映射
* @since 4.1.10
*/
private Map<String, String> getReversedMapping() {
if (null == this.fieldMapping) {
return null;
}
if (null == this.reversedFieldMapping) {
reversedFieldMapping = MapUtil.reverse(this.fieldMapping);
}
return reversedFieldMapping;
}
} }

View File

@ -3,9 +3,12 @@ package cn.hutool.core.bean.copier.provider;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.PropDesc; import cn.hutool.core.bean.PropDesc;
import cn.hutool.core.bean.copier.ValueProvider; 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 cn.hutool.core.util.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -27,9 +30,32 @@ public class BeanValueProvider implements ValueProvider<String> {
* @param ignoreError 是否忽略字段值读取错误 * @param ignoreError 是否忽略字段值读取错误
*/ */
public BeanValueProvider(Object bean, boolean ignoreCase, boolean 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.source = bean;
this.ignoreError = ignoreError; this.ignoreError = ignoreError;
sourcePdMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(ignoreCase); 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 @Override

View File

@ -2,10 +2,12 @@ package cn.hutool.core.bean.copier.provider;
import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.lang.Editor;
import cn.hutool.core.map.FuncKeyMap;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -17,13 +19,13 @@ import java.util.Map;
* <li>isFirstName如果为Boolean或boolean类型的值</li> * <li>isFirstName如果为Boolean或boolean类型的值</li>
* <li>is_first_name如果为Boolean或boolean类型的值</li> * <li>is_first_name如果为Boolean或boolean类型的值</li>
* </ul> * </ul>
* 为firstName或first_name都可以对应到值
* *
* @author looly * @author looly
*/ */
public class MapValueProvider implements ValueProvider<String> { public class MapValueProvider implements ValueProvider<String> {
private final Map<?, ?> map; @SuppressWarnings("rawtypes")
private final Map map;
private final boolean ignoreError; private final boolean ignoreError;
/** /**
@ -45,13 +47,33 @@ public class MapValueProvider implements ValueProvider<String> {
* @since 5.3.2 * @since 5.3.2
*/ */
public MapValueProvider(Map<?, ?> map, boolean ignoreCase, boolean ignoreError) { public MapValueProvider(Map<?, ?> map, boolean ignoreCase, boolean ignoreError) {
if (false == ignoreCase || map instanceof CaseInsensitiveMap) { this(map, ignoreCase, ignoreError, null);
//不忽略大小写或者提供的Map本身为CaseInsensitiveMap则无需转换 }
this.map = map;
} else { /**
//转换为大小写不敏感的Map * 构造
this.map = new CaseInsensitiveMap<>(map); *
} * @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(new HashMap(map.size(), 1), (key)->{
if(ignoreCase && key instanceof CharSequence){
key = key.toString().toLowerCase();
}
if(null != keyEditor){
key = keyEditor.edit(key.toString());
}
return key;
});
this.map.putAll(map);
this.ignoreError = ignoreError; this.ignoreError = ignoreError;
} }

View File

@ -42,6 +42,20 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
} }
} }
@Override
public V remove(Object key) {
final V v = super.remove(key);
if(null != this.inverse && null != v){
this.inverse.remove(v);
}
return v;
}
@Override
public boolean remove(Object key, Object value) {
return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key);
}
@Override @Override
public void clear() { public void clear() {
super.clear(); super.clear();

View File

@ -713,7 +713,7 @@ public class BeanUtilTest {
CopyOptions copyOptions = CopyOptions.create(). CopyOptions copyOptions = CopyOptions.create().
//setIgnoreNullValue(true). //setIgnoreNullValue(true).
//setIgnoreCase(false). //setIgnoreCase(false).
setFieldNameEditor(StrUtil::toUnderlineCase); setFieldNameEditor(StrUtil::toCamelCase);
ChildVo2 childVo2 = new ChildVo2(); ChildVo2 childVo2 = new ChildVo2();
BeanUtil.copyProperties(childVo1, childVo2, copyOptions); BeanUtil.copyProperties(childVo1, childVo2, copyOptions);

View File

@ -1,9 +1,9 @@
package cn.hutool.core.bean; package cn.hutool.core.bean;
import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Console;
import cn.hutool.core.text.NamingCase; import cn.hutool.core.text.NamingCase;
import lombok.Data; import lombok.Data;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap; import java.util.HashMap;
@ -11,15 +11,23 @@ import java.util.Map;
public class Issue2202Test { public class Issue2202Test {
/**
* https://github.com/dromara/hutool/issues/2202
*/
@Test @Test
public void toBeanWithFieldNameEditorTest(){ public void mapToBeanWithFieldNameEditorTest(){
Map<String, String> headerMap = new HashMap<>(5); Map<String, String> headerMap = new HashMap<>(5);
headerMap.put("wechatpay-serial", "serial"); headerMap.put("wechatpay-serial", "serial");
headerMap.put("wechatpay-nonce", "nonce"); headerMap.put("wechatpay-nonce", "nonce");
headerMap.put("wechatpay-timestamp", "timestamp"); headerMap.put("wechatpay-timestamp", "timestamp");
headerMap.put("wechatpay-signature", "signature"); headerMap.put("wechatpay-signature", "signature");
ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class, CopyOptions.create().setFieldNameEditor(field -> NamingCase.toCamelCase(field, '-'))); ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class,
Console.log(case1); CopyOptions.create().setFieldNameEditor(field -> NamingCase.toCamelCase(field, '-')));
Assert.assertEquals("serial", case1.getWechatpaySerial());
Assert.assertEquals("nonce", case1.getWechatpayNonce());
Assert.assertEquals("timestamp", case1.getWechatpayTimestamp());
Assert.assertEquals("signature", case1.getWechatpaySignature());
} }
@Data @Data