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
* 【http 】 修复GET重定向时携带参数问题issue#2189@Github
* 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题pr#2188@Github
* 【core 】 修复CopyOptions中fieldNameEditor无效问题issue#2202@Github
-------------------------------------------------------------------------------------------------------------
# 5.7.22 (2022-03-01)

View File

@ -118,7 +118,11 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
* @since 5.4.2
*/
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
*/
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) {
valueProviderToBean(
new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError),
new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor),
bean
);
}
@ -161,7 +162,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return;
}
// key做映射映射后为null的忽略之
// key做映射映射后为null的忽略之
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
if(null == key){
return;
@ -280,12 +281,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
}
// 对key做映射映射后为null的忽略之
// 这里 copyOptions.editFieldName() 不能少否则导致 CopyOptions setFieldNameEditor 失效
final String providerKey = copyOptions.editFieldName(copyOptions.getMappedFieldName(fieldName, true));
if(null == providerKey){
final String sourceKey = copyOptions.getMappedFieldName(fieldName, true);
if(null == sourceKey){
return;
}
if (false == valueProvider.containsKey(providerKey)) {
if (false == valueProvider.containsKey(sourceKey)) {
// 无对应值可提供
return;
}
@ -294,13 +294,13 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
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)) {
return;
}
// since 5.7.15
value = copyOptions.editFieldValue(providerKey, value);
value = copyOptions.editFieldValue(sourceKey, value);
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
// 当允许跳过空时跳过

View File

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

View File

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

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
public void clear() {
super.clear();

View File

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

View File

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