修复BeanUtil.copyProperties中mapToMap时key被转为String问题

This commit is contained in:
Looly 2024-07-06 11:36:34 +08:00
parent 2fffc07c0f
commit ca16ad9f1d
15 changed files with 84 additions and 33 deletions

View File

@ -373,12 +373,12 @@ public class BeanUtil {
*/ */
public static Map<String, Object> beanToMap(final Object bean, final String... properties) { public static Map<String, Object> beanToMap(final Object bean, final String... properties) {
int mapSize = 16; int mapSize = 16;
UnaryOperator<MutableEntry<String, Object>> editor = null; UnaryOperator<MutableEntry<Object, Object>> editor = null;
if (ArrayUtil.isNotEmpty(properties)) { if (ArrayUtil.isNotEmpty(properties)) {
mapSize = properties.length; mapSize = properties.length;
final Set<String> propertiesSet = SetUtil.of(properties); final Set<String> propertiesSet = SetUtil.of(properties);
editor = entry -> { editor = entry -> {
final String key = entry.getKey(); final String key = StrUtil.toStringOrNull(entry.getKey());
entry.setKey(propertiesSet.contains(key) ? key : null); entry.setKey(propertiesSet.contains(key) ? key : null);
return entry; return entry;
}; };
@ -413,13 +413,14 @@ public class BeanUtil {
* @return Map * @return Map
* @since 3.2.3 * @since 3.2.3
*/ */
public static Map<String, Object> beanToMap(final Object bean, final Map<String, Object> targetMap, final boolean isToUnderlineCase, final boolean ignoreNullValue) { public static Map<String, Object> beanToMap(final Object bean, final Map<String, Object> targetMap,
final boolean isToUnderlineCase, final boolean ignoreNullValue) {
if (null == bean) { if (null == bean) {
return null; return null;
} }
return beanToMap(bean, targetMap, ignoreNullValue, entry -> { return beanToMap(bean, targetMap, ignoreNullValue, entry -> {
final String key = entry.getKey(); final String key = StrUtil.toStringOrNull(entry.getKey());
entry.setKey(isToUnderlineCase ? StrUtil.toUnderlineCase(key) : key); entry.setKey(isToUnderlineCase ? StrUtil.toUnderlineCase(key) : key);
return entry; return entry;
}); });
@ -442,8 +443,10 @@ public class BeanUtil {
* @return Map * @return Map
* @since 4.0.5 * @since 4.0.5
*/ */
public static Map<String, Object> beanToMap(final Object bean, final Map<String, Object> targetMap, public static Map<String, Object> beanToMap(final Object bean,
final boolean ignoreNullValue, final UnaryOperator<MutableEntry<String, Object>> keyEditor) { final Map<String, Object> targetMap,
final boolean ignoreNullValue,
final UnaryOperator<MutableEntry<Object, Object>> keyEditor) {
if (null == bean) { if (null == bean) {
return null; return null;
} }

View File

@ -17,6 +17,7 @@ import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.reflect.TypeUtil; import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -73,11 +74,11 @@ public class BeanToBeanCopier<S, T> extends AbsCopier<S, T> {
} }
// 编辑键值对 // 编辑键值对
final MutableEntry<String, Object> entry = copyOptions.editField(sFieldName, sValue); final MutableEntry<Object, Object> entry = copyOptions.editField(sFieldName, sValue);
if(null == entry){ if(null == entry){
return; return;
} }
sFieldName = entry.getKey(); sFieldName = StrUtil.toStringOrNull(entry.getKey());
// 对key做转换转换后为null的跳过 // 对key做转换转换后为null的跳过
if (null == sFieldName) { if (null == sFieldName) {
return; return;

View File

@ -17,6 +17,7 @@ import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.reflect.TypeUtil; import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -72,11 +73,11 @@ public class BeanToMapCopier extends AbsCopier<Object, Map> {
} }
// 编辑键值对 // 编辑键值对
final MutableEntry<String, Object> entry = copyOptions.editField(sFieldName, sValue); final MutableEntry<Object, Object> entry = copyOptions.editField(sFieldName, sValue);
if(null == entry){ if(null == entry){
return; return;
} }
sFieldName = entry.getKey(); sFieldName = StrUtil.toStringOrNull(entry.getKey());
// 对key做转换转换后为null的跳过 // 对key做转换转换后为null的跳过
if (null == sFieldName) { if (null == sFieldName) {
return; return;

View File

@ -67,7 +67,7 @@ public class CopyOptions implements Serializable {
/** /**
* 字段属性名和属性值编辑器用于自定义属性转换规则例如驼峰转下划线等自定义属性值转换规则例如null转"" * 字段属性名和属性值编辑器用于自定义属性转换规则例如驼峰转下划线等自定义属性值转换规则例如null转""
*/ */
protected UnaryOperator<MutableEntry<String, Object>> fieldEditor; protected UnaryOperator<MutableEntry<Object, Object>> fieldEditor;
/** /**
* 是否支持transient关键字修饰和@Transient注解如果支持被修饰的字段或方法对应的字段将被忽略 * 是否支持transient关键字修饰和@Transient注解如果支持被修饰的字段或方法对应的字段将被忽略
@ -264,10 +264,11 @@ public class CopyOptions implements Serializable {
* @param fieldMapping 拷贝属性的字段映射用于不同的属性之前拷贝做对应表用 * @param fieldMapping 拷贝属性的字段映射用于不同的属性之前拷贝做对应表用
* @return CopyOptions * @return CopyOptions
*/ */
public CopyOptions setFieldMapping(final Map<String, String> fieldMapping) { public CopyOptions setFieldMapping(final Map<?, ?> fieldMapping) {
return setFieldEditor(entry -> { return setFieldEditor(entry -> {
final String key = entry.getKey(); final Object key = entry.getKey();
entry.setKey(fieldMapping.getOrDefault(key, key)); final Object keyMapped = fieldMapping.get(key);
entry.setKey(null == keyMapped ? key : keyMapped);
return entry; return entry;
}); });
} }
@ -282,7 +283,7 @@ public class CopyOptions implements Serializable {
* @return CopyOptions * @return CopyOptions
* @since 5.4.2 * @since 5.4.2
*/ */
public CopyOptions setFieldEditor(final UnaryOperator<MutableEntry<String, Object>> editor) { public CopyOptions setFieldEditor(final UnaryOperator<MutableEntry<Object, Object>> editor) {
this.fieldEditor = editor; this.fieldEditor = editor;
return this; return this;
} }
@ -290,13 +291,13 @@ public class CopyOptions implements Serializable {
/** /**
* 编辑字段值 * 编辑字段值
* *
* @param fieldName 字段名 * @param key 字段名
* @param fieldValue 字段值 * @param value 字段值
* @return 编辑后的字段值 * @return 编辑后的字段值
* @since 5.7.15 * @since 5.7.15
*/ */
protected MutableEntry<String, Object> editField(final String fieldName, final Object fieldValue) { protected MutableEntry<Object, Object> editField(final Object key, final Object value) {
final MutableEntry<String, Object> entry = new MutableEntry<>(fieldName, fieldValue); final MutableEntry<Object, Object> entry = new MutableEntry<>(key, value);
return (null != this.fieldEditor) ? return (null != this.fieldEditor) ?
this.fieldEditor.apply(entry) : entry; this.fieldEditor.apply(entry) : entry;
} }

View File

@ -19,7 +19,6 @@ import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.map.CaseInsensitiveMap; import org.dromara.hutool.core.map.CaseInsensitiveMap;
import org.dromara.hutool.core.map.MapWrapper; import org.dromara.hutool.core.map.MapWrapper;
import org.dromara.hutool.core.reflect.TypeUtil; import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -76,11 +75,11 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
} }
// 编辑键值对 // 编辑键值对
final MutableEntry<String, Object> entry = copyOptions.editField(sKey.toString(), sValue); final MutableEntry<Object, Object> entry = copyOptions.editField(sKey, sValue);
if(null == entry){ if(null == entry){
return; return;
} }
final String sFieldName = entry.getKey(); final Object sFieldName = entry.getKey();
// 对key做转换转换后为null的跳过 // 对key做转换转换后为null的跳过
if (null == sFieldName) { if (null == sFieldName) {
return; return;
@ -88,7 +87,7 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
// 检查目标字段可写性 // 检查目标字段可写性
// 目标字段检查放在键值对编辑之后因为键可能被编辑修改 // 目标字段检查放在键值对编辑之后因为键可能被编辑修改
final PropDesc tDesc = this.copyOptions.findPropDesc(targetPropDescMap, sFieldName); final PropDesc tDesc = this.copyOptions.findPropDesc(targetPropDescMap, sFieldName.toString());
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) { if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
// 字段不可写跳过之 // 字段不可写跳过之
return; return;

View File

@ -52,7 +52,7 @@ public class MapToMapCopier extends AbsCopier<Map, Map> {
} }
// 编辑键值对 // 编辑键值对
final MutableEntry<String, Object> entry = copyOptions.editField(sKey.toString(), sValue); final MutableEntry<Object, Object> entry = copyOptions.editField(sKey, sValue);
if(null == entry){ if(null == entry){
return; return;
} }

View File

@ -17,6 +17,7 @@ import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.reflect.TypeUtil; import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -72,11 +73,11 @@ public class ValueProviderToBeanCopier<T> extends AbsCopier<ValueProvider<String
// 获取目标字段真实类型 // 获取目标字段真实类型
final Type fieldType = TypeUtil.getActualType(this.targetType ,tDesc.getFieldType()); final Type fieldType = TypeUtil.getActualType(this.targetType ,tDesc.getFieldType());
// 编辑键值对 // 编辑键值对
final MutableEntry<String, Object> entry = copyOptions.editField(tFieldName, null); final MutableEntry<Object, Object> entry = copyOptions.editField(tFieldName, null);
if(null == entry){ if(null == entry){
return; return;
} }
tFieldName = entry.getKey(); tFieldName = StrUtil.toStringOrNull(entry.getKey());
// 对key做转换转换后为null的跳过 // 对key做转换转换后为null的跳过
if (null == tFieldName) { if (null == tFieldName) {
return; return;

View File

@ -374,7 +374,7 @@ public class WordTree extends HashMap<Character, WordTree> {
private Iterable<String> innerFlatten(final Entry<Character, WordTree> entry) { private Iterable<String> innerFlatten(final Entry<Character, WordTree> entry) {
final List<String> list = EasyStream.of(entry.getValue().entrySet()).flat(this::innerFlatten).map(v -> entry.getKey() + v).toList(); final List<String> list = EasyStream.of(entry.getValue().entrySet()).flat(this::innerFlatten).map(v -> entry.getKey() + v).toList();
if (list.isEmpty()) { if (list.isEmpty()) {
return EasyStream.of(entry.getKey().toString()); return EasyStream.of(StrUtil.toStringOrNull(entry.getKey()));
} }
return list; return list;
} }

View File

@ -879,7 +879,7 @@ public class BeanUtilTest {
//setIgnoreNullValue(true). //setIgnoreNullValue(true).
//setIgnoreCase(false). //setIgnoreCase(false).
setFieldEditor(entry->{ setFieldEditor(entry->{
entry.setKey(StrUtil.toCamelCase(entry.getKey())); entry.setKey(StrUtil.toCamelCase(entry.getKey().toString()));
return entry; return entry;
}); });

View File

@ -46,7 +46,7 @@ public class Issue1687Test {
// 补救别名错位 // 补救别名错位
final CopyOptions copyOptions = CopyOptions.of().setFieldMapping( final CopyOptions copyOptions = CopyOptions.of().setFieldMapping(
MapUtil.builder("depart", "depId").build() MapUtil.builder((Object)"depart", (Object)"depId").build()
); );
final SysUser sysUser = BeanUtil.toBean(sysUserFb, SysUser.class, copyOptions); final SysUser sysUser = BeanUtil.toBean(sysUserFb, SysUser.class, copyOptions);

View File

@ -35,7 +35,7 @@ public class Issue2202Test {
headerMap.put("wechatpay-signature", "signature"); headerMap.put("wechatpay-signature", "signature");
final ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class, final ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class,
CopyOptions.of().setFieldEditor(entry -> { CopyOptions.of().setFieldEditor(entry -> {
entry.setKey(NamingCase.toCamelCase(entry.getKey(), '-')); entry.setKey(NamingCase.toCamelCase(entry.getKey().toString(), '-'));
return entry; return entry;
})); }));

View File

@ -14,7 +14,7 @@ public class Issue3497Test {
public void setFieldEditorTest() { public void setFieldEditorTest() {
final Map<String, String> aB = MapUtil.builder("a_b", "1").build(); final Map<String, String> aB = MapUtil.builder("a_b", "1").build();
final Map<?, ?> bean = BeanUtil.toBean(aB, Map.class, CopyOptions.of().setFieldEditor((entry)->{ final Map<?, ?> bean = BeanUtil.toBean(aB, Map.class, CopyOptions.of().setFieldEditor((entry)->{
entry.setKey(StrUtil.toCamelCase(entry.getKey())); entry.setKey(StrUtil.toCamelCase(entry.getKey().toString()));
return entry; return entry;
})); }));
Assertions.assertEquals(bean.toString(), "{aB=1}"); Assertions.assertEquals(bean.toString(), "{aB=1}");

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024. 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.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class Issue3645Test {
@Test
public void copyPropertiesTest() {
final User p = new User();
p.setUserId(123L);
final Map<Long, User> map = new HashMap<>();
map.put(123L,p);
final Map<Long, User> m = new HashMap<>();
BeanUtil.copyProperties(map, m);
final User u = m.get(123L);
assertNotNull(u);
}
@Data
static class User{
private Long userId;
}
}

View File

@ -648,7 +648,7 @@ public class SoapClient implements HeaderOperation<SoapClient> {
Entry entry; Entry entry;
for (final Object obj : ((Map) value).entrySet()) { for (final Object obj : ((Map) value).entrySet()) {
entry = (Entry) obj; entry = (Entry) obj;
setParam(childEle, entry.getKey().toString(), entry.getValue(), prefix); setParam(childEle, StrUtil.toStringOrNull(entry.getKey()), entry.getValue(), prefix);
} }
} else { } else {
// 单个值 // 单个值

View File

@ -208,7 +208,9 @@ public class JSONObjectMapper {
private void mapFromBean(final Object bean, final JSONObject jsonObject) { private void mapFromBean(final Object bean, final JSONObject jsonObject) {
final CopyOptions copyOptions = InternalJSONUtil.toCopyOptions(jsonObject.config()); final CopyOptions copyOptions = InternalJSONUtil.toCopyOptions(jsonObject.config());
if (null != this.predicate) { if (null != this.predicate) {
copyOptions.setFieldEditor((entry -> this.predicate.test(entry) ? entry : null)); copyOptions.setFieldEditor((entry -> this.predicate.test(
MutableEntry.of(StrUtil.toStringOrNull(entry.getKey()), entry.getValue())) ?
entry : null));
} }
BeanUtil.beanToMap(bean, jsonObject, copyOptions); BeanUtil.beanToMap(bean, jsonObject, copyOptions);
} }