diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/AbstractConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/AbstractConverter.java
index e64e4b31a..1be4cf73d 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/AbstractConverter.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/AbstractConverter.java
@@ -12,18 +12,23 @@
package org.dromara.hutool.core.convert;
-import org.dromara.hutool.core.lang.Assert;
-import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.array.ArrayUtil;
+import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.util.CharUtil;
import java.io.Serializable;
import java.lang.reflect.Type;
-import java.util.Map;
/**
* 抽象转换器,提供通用的转换逻辑,同时通过convertInternal实现对应类型的专属逻辑
- * 转换器不会抛出转换异常,转换失败时会返回{@code null}
+ * 转换器不会抛出转换异常,转换失败时会返回{@code null}
+ * 抽象转换器的默认逻辑不适用于有泛型参数的对象,如Map、Collection、Entry等。通用逻辑包括:
+ *
+ * - value为{@code null}时返回{@code null}
+ * - 目标类型是{@code null}或者{@link java.lang.reflect.TypeVariable}时,抛出{@link ConvertException}异常
+ * - 目标类型非class时,抛出{@link IllegalArgumentException}
+ * - 目标类型为值的父类或同类,直接强转返回
+ *
*
* @author Looly
*/
@@ -31,19 +36,21 @@ public abstract class AbstractConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
@Override
- public Object convert(final Type targetType, final Object value) {
+ public Object convert(final Type targetType, final Object value) throws ConvertException{
if (null == value) {
return null;
}
if (TypeUtil.isUnknown(targetType)) {
- throw new ConvertException("Unsupported convert to unKnow type: {}", targetType);
+ throw new ConvertException("Unsupported convert to unKnown type: {}", targetType);
}
final Class> targetClass = TypeUtil.getClass(targetType);
- Assert.notNull(targetClass, "Target type is not a class!");
+ if(null == targetClass){
+ throw new ConvertException("Target type [{}] is not a class!", targetType);
+ }
// 尝试强转
- if (targetClass.isInstance(value) && false == Map.class.isAssignableFrom(targetClass)) {
+ if (targetClass.isInstance(value)) {
// 除Map外,已经是目标类型,不需要转换(Map类型涉及参数类型,需要单独转换)
return CastUtil.castTo(targetClass, value);
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java
index 163534e06..c49b77eee 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java
@@ -13,13 +13,7 @@
package org.dromara.hutool.core.convert;
import org.dromara.hutool.core.bean.BeanUtil;
-import org.dromara.hutool.core.convert.impl.ArrayConverter;
-import org.dromara.hutool.core.convert.impl.BeanConverter;
-import org.dromara.hutool.core.convert.impl.CollectionConverter;
-import org.dromara.hutool.core.convert.impl.EnumConverter;
-import org.dromara.hutool.core.convert.impl.MapConverter;
-import org.dromara.hutool.core.convert.impl.NumberConverter;
-import org.dromara.hutool.core.convert.impl.PrimitiveConverter;
+import org.dromara.hutool.core.convert.impl.*;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.util.ObjUtil;
@@ -195,6 +189,11 @@ public class CompositeConverter extends RegisterConverter {
return (T) MapConverter.INSTANCE.convert(type, value, (Map, ?>) defaultValue);
}
+ // issue#I6SZYB Entry类(含有泛型参数,不可以默认强转)
+ if(Map.Entry.class.isAssignableFrom(rowType)){
+ return (T) EntryConverter.INSTANCE.convert(type, value);
+ }
+
// 默认强转
if (rowType.isInstance(value)) {
return (T) value;
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java
new file mode 100644
index 000000000..57e69cc27
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java
@@ -0,0 +1,139 @@
+/*
+ * 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:
+ * http://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.convert.impl;
+
+import org.dromara.hutool.core.bean.BeanUtil;
+import org.dromara.hutool.core.convert.CompositeConverter;
+import org.dromara.hutool.core.convert.ConvertException;
+import org.dromara.hutool.core.convert.Converter;
+import org.dromara.hutool.core.map.MapUtil;
+import org.dromara.hutool.core.reflect.ConstructorUtil;
+import org.dromara.hutool.core.reflect.TypeReference;
+import org.dromara.hutool.core.reflect.TypeUtil;
+import org.dromara.hutool.core.text.StrUtil;
+import org.dromara.hutool.core.util.CharUtil;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * {@link Map.Entry} 转换器,支持以下类型转为Entry
+ *
+ * - {@link Map}
+ * - {@link Map.Entry}
+ * - 带分隔符的字符串,支持分隔符{@code :}、{@code =}、{@code ,}
+ * - Bean,包含{@code getKey}和{@code getValue}方法
+ *
+ *
+ * @author looly
+ */
+public class EntryConverter implements Converter {
+
+ /**
+ * 单例
+ */
+ public static final EntryConverter INSTANCE = new EntryConverter();
+
+ @Override
+ public Object convert(Type targetType, final Object value) throws ConvertException {
+ if (targetType instanceof TypeReference) {
+ targetType = ((TypeReference>) targetType).getType();
+ }
+ final Type keyType = TypeUtil.getTypeArgument(targetType, 0);
+ final Type valueType = TypeUtil.getTypeArgument(targetType, 1);
+
+ return convert(targetType, keyType, valueType, value);
+ }
+
+ /**
+ * 转换对象为指定键值类型的指定类型Map
+ *
+ * @param targetType 目标的Map类型
+ * @param keyType 键类型
+ * @param valueType 值类型
+ * @param value 被转换的值
+ * @return 转换后的Map
+ * @throws ConvertException 转换异常或不支持的类型
+ */
+ @SuppressWarnings("rawtypes")
+ public Map.Entry, ?> convert(final Type targetType, final Type keyType, final Type valueType, final Object value)
+ throws ConvertException {
+ Map map = null;
+ if (value instanceof Map.Entry) {
+ final Map.Entry entry = (Map.Entry) value;
+ map = MapUtil.of(entry.getKey(), entry.getValue());
+ }
+ if (value instanceof Map) {
+ map = (Map) value;
+ } else if (value instanceof CharSequence) {
+ final CharSequence str = (CharSequence) value;
+ map = strToMap(str);
+ } else if (BeanUtil.isBean(value.getClass())) {
+ map = BeanUtil.beanToMap(value);
+ }
+
+ if (null != map) {
+ return mapToEntry(targetType, keyType, valueType, map);
+ }
+
+ throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName());
+ }
+
+ /**
+ * 字符串转单个键值对的Map,支持分隔符{@code :}、{@code =}、{@code ,}
+ *
+ * @param str 字符串
+ * @return map or null
+ */
+ private static Map strToMap(final CharSequence str) {
+ // key:value key=value key,value
+ final int index = StrUtil.indexOf(str,
+ c -> c == CharUtil.COLON || c == CharUtil.EQUAL || c == CharUtil.COMMA,
+ 0, str.length());
+
+ if (index > -1) {
+ return MapUtil.of(str.subSequence(0, index + 1), str.subSequence(index, str.length()));
+ }
+ return null;
+ }
+
+ /**
+ * Map转Entry
+ *
+ * @param targetType 目标的Map类型
+ * @param keyType 键类型
+ * @param valueType 值类型
+ * @param map 被转换的map
+ * @return Entry
+ */
+ @SuppressWarnings("rawtypes")
+ private static Map.Entry, ?> mapToEntry(final Type targetType, final Type keyType, final Type valueType, final Map map) {
+
+ Object key = null;
+ Object value = null;
+ if (1 == map.size()) {
+ final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
+ key = entry.getKey();
+ value = entry.getValue();
+ } else if (2 == map.size()) {
+ key = map.get("key");
+ value = map.get("value");
+ }
+
+ final CompositeConverter convert = CompositeConverter.getInstance();
+ return (Map.Entry, ?>) ConstructorUtil.newInstance(TypeUtil.getClass(targetType),
+ TypeUtil.isUnknown(keyType) ? key : convert.convert(keyType, key),
+ TypeUtil.isUnknown(valueType) ? value : convert.convert(valueType, value)
+ );
+ }
+}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/MapConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/MapConverter.java
index 74609ec1d..1a5b1627d 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/MapConverter.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/MapConverter.java
@@ -13,13 +13,12 @@
package org.dromara.hutool.core.convert.impl;
import org.dromara.hutool.core.bean.BeanUtil;
-import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.CompositeConverter;
+import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
-import org.dromara.hutool.core.text.StrUtil;
import java.io.Serializable;
import java.lang.reflect.Type;
@@ -27,7 +26,11 @@ import java.util.Map;
import java.util.Objects;
/**
- * {@link Map} 转换器
+ * {@link Map} 转换器,通过预定义key和value的类型,实现:
+ *
+ * - Map 转 Map,key和value类型自动转换
+ * - Bean 转 Map,字段和字段值类型自动转换
+ *
*
* @author Looly
* @since 3.0.8
@@ -35,6 +38,9 @@ import java.util.Objects;
public class MapConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
+ /**
+ * 单例
+ */
public static MapConverter INSTANCE = new MapConverter();
@Override
@@ -56,9 +62,11 @@ public class MapConverter implements Converter, Serializable {
* @param valueType 值类型
* @param value 被转换的值
* @return 转换后的Map
+ * @throws ConvertException 转换异常或不支持的类型
*/
@SuppressWarnings("rawtypes")
- public Map, ?> convert(final Type targetType, final Type keyType, final Type valueType, final Object value) {
+ public Map, ?> convert(final Type targetType, final Type keyType, final Type valueType, final Object value)
+ throws ConvertException{
Map map;
if (value instanceof Map) {
final Class> valueClass = value.getClass();
@@ -79,7 +87,7 @@ public class MapConverter implements Converter, Serializable {
// 二次转换,转换键值类型
map = convert(targetType, keyType, valueType, map);
} else {
- throw new UnsupportedOperationException(StrUtil.format("Unsupported toMap value type: {}", value.getClass().getName()));
+ throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName());
}
return map;
}
@@ -93,10 +101,9 @@ public class MapConverter implements Converter, Serializable {
@SuppressWarnings({"rawtypes", "unchecked"})
private void convertMapToMap(final Type keyType, final Type valueType, final Map, ?> srcMap, final Map targetMap) {
final CompositeConverter convert = CompositeConverter.getInstance();
- srcMap.forEach((key, value) -> {
- key = TypeUtil.isUnknown(keyType) ? key : convert.convert(keyType, key, null);
- value = TypeUtil.isUnknown(valueType) ? value : convert.convert(valueType, value, null);
- targetMap.put(key, value);
- });
+ srcMap.forEach((key, value) -> targetMap.put(
+ TypeUtil.isUnknown(keyType) ? key : convert.convert(keyType, key),
+ TypeUtil.isUnknown(valueType) ? value : convert.convert(valueType, value)
+ ));
}
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/package-info.java
index 9a5d7694c..f9395d10e 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/package-info.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/package-info.java
@@ -13,6 +13,11 @@
/**
* 万能类型转换器以及各种类型转换的实现类,其中Convert为转换器入口,提供各种toXXX方法和convert方法
*
+ *
+ * 转换器是典型的策略模式应用,通过实现{@link org.dromara.hutool.core.convert.Converter} 接口,
+ * 自定义转换策略。Hutool提供了常用类型的转换策略。
+ *
+ *
* @author looly
*
*/
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableEntry.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableEntry.java
index 5e64adf00..7846b491a 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableEntry.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableEntry.java
@@ -27,6 +27,19 @@ import java.util.Map;
public class MutableEntry extends AbsEntry implements Mutable>, Serializable {
private static final long serialVersionUID = 1L;
+ /**
+ * 创建{@code MutableEntry}
+ *
+ * @param key 键
+ * @param value 值
+ * @param 键类型
+ * @param 值类型
+ * @return
+ */
+ public static MutableEntry of(final K key, final V value) {
+ return new MutableEntry<>(key, value);
+ }
+
protected K key;
protected V value;
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSONUtil.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSONUtil.java
index 1c6f20df6..2eaf52c6f 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/JSONUtil.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSONUtil.java
@@ -40,6 +40,7 @@ import java.util.List;
public class JSONUtil {
// -------------------------------------------------------------------- Pause start
+
/**
* 创建JSONObject
*
@@ -169,7 +170,7 @@ public class JSONUtil {
* @return JSON(JSONObject or JSONArray)
*/
public static Object parse(final Object obj, final JSONConfig config) {
- if(null == config){
+ if (null == config) {
return JSONConverter.INSTANCE.toJSON(obj);
}
return JSONConverter.of(config).toJSON(obj);
@@ -226,6 +227,7 @@ public class JSONUtil {
// -------------------------------------------------------------------- Read end
// -------------------------------------------------------------------- toString start
+
/**
* 转换为格式化后的JSON字符串
*
@@ -270,7 +272,7 @@ public class JSONUtil {
return stringWriter.toString();
}
- if(null == obj){
+ if (null == obj) {
return null;
}
return parse(obj, jsonConfig).toString();
@@ -323,48 +325,18 @@ public class JSONUtil {
// -------------------------------------------------------------------- toString end
// -------------------------------------------------------------------- toBean start
-
/**
- * JSON字符串转为实体类对象,转换异常将被抛出
+ * 转为实体类对象
*
- * @param Bean类型
- * @param jsonString JSON字符串
- * @param beanClass 实体类对象
+ * @param Bean类型
+ * @param json JSONObject
+ * @param clazz 实体类
* @return 实体类对象
- * @since 3.1.2
+ * @since 4.6.2
*/
- public static T toBean(final String jsonString, final Class beanClass) {
- return toBean(parse(jsonString), beanClass);
- }
-
- /**
- * JSON字符串转为实体类对象,转换异常将被抛出
- * 通过{@link JSONConfig}可选是否忽略大小写、忽略null等配置
- *
- * @param Bean类型
- * @param jsonString JSON字符串
- * @param config JSON配置
- * @param beanClass 实体类对象
- * @return 实体类对象
- * @since 5.8.0
- */
- public static T toBean(final String jsonString, final JSONConfig config, final Class beanClass) {
- return toBean(jsonString, config, (Type) beanClass);
- }
-
- /**
- * JSON字符串转为实体类对象,转换异常将被抛出
- * 通过{@link JSONConfig}可选是否忽略大小写、忽略null等配置
- *
- * @param Bean类型
- * @param jsonString JSON字符串
- * @param config JSON配置
- * @param type Bean类型
- * @return 实体类对象
- * @throws JSONException 提供的JSON字符串不支持转Bean或字符串错误
- */
- public static T toBean(final String jsonString, final JSONConfig config, final Type type) throws JSONException {
- return toBean(parse(jsonString, config), type);
+ public static T toBean(final Object json, final Class clazz) {
+ Assert.notNull(clazz);
+ return toBean(json, (Type)clazz);
}
/**
@@ -391,12 +363,31 @@ public class JSONUtil {
* @since 4.3.2
*/
public static T toBean(final Object json, final Type type) {
+ return toBean(json, null, type);
+ }
+
+ /**
+ * 转为实体类对象
+ *
+ * @param Bean类型
+ * @param json JSONObject
+ * @param config JSON配置
+ * @param type 实体类对象类型
+ * @return 实体类对象
+ * @since 4.3.2
+ */
+ public static T toBean(Object json, final JSONConfig config, Type type) {
if (null == json) {
return null;
}
+ json = parse(json, config);
if (json instanceof JSON) {
+ if (type instanceof TypeReference) {
+ type = ((TypeReference>) type).getType();
+ }
return ((JSON) json).toBean(type);
}
+
throw new JSONException("Unsupported json string to bean : {}", json);
}
// -------------------------------------------------------------------- toBean end
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
index c2f8cdf85..db1459d6c 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
@@ -12,6 +12,7 @@
package org.dromara.hutool.json.convert;
+import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.copier.BeanCopier;
import org.dromara.hutool.core.convert.Convert;
@@ -24,10 +25,8 @@ import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.text.StrUtil;
-import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.*;
-import org.dromara.hutool.json.*;
import org.dromara.hutool.json.serialize.JSONDeserializer;
import org.dromara.hutool.json.serialize.JSONStringer;
@@ -233,6 +232,7 @@ public class JSONConverter implements Converter {
*
* Collection
* Map
+ * Map.Entry
* 强转(无需转换)
* 数组
*
@@ -258,6 +258,11 @@ public class JSONConverter implements Converter {
return (T) MapConverter.INSTANCE.convert(type, value);
}
+ // issue#I6SZYB Entry类(含有泛型参数,不可以默认强转)
+ if(Map.Entry.class.isAssignableFrom(rowType)){
+ return (T) EntryConverter.INSTANCE.convert(type, value);
+ }
+
// 默认强转
if (rowType.isInstance(value)) {
return (T) value;
diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/IssueI6SZYBTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI6SZYBTest.java
new file mode 100644
index 000000000..c2a4bcf1b
--- /dev/null
+++ b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI6SZYBTest.java
@@ -0,0 +1,51 @@
+package org.dromara.hutool.json;
+
+import org.dromara.hutool.core.lang.mutable.MutableEntry;
+import org.dromara.hutool.core.reflect.TypeReference;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.AbstractMap;
+
+public class IssueI6SZYBTest {
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void mutableEntryTest() {
+ final MutableEntry entry = MutableEntry.of("a", "b");
+ final String jsonStr = JSONUtil.toJsonStr(entry);
+ final MutableEntry entry2 = JSONUtil.toBean(jsonStr, MutableEntry.class);
+
+ Assertions.assertEquals(entry, entry2);
+ }
+
+ @Test
+ void mutableEntryTest2() {
+ final MutableEntry entry = MutableEntry.of(1, 2);
+ final String jsonStr = JSONUtil.toJsonStr(entry);
+ final MutableEntry entry2 = JSONUtil.toBean(jsonStr,
+ new TypeReference>() {});
+
+ Assertions.assertEquals(entry, entry2);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void simpleEntryTest() {
+ final AbstractMap.SimpleEntry entry = new AbstractMap.SimpleEntry<>("a", "b");
+ final String jsonStr = JSONUtil.toJsonStr(entry);
+ final AbstractMap.SimpleEntry entry2 = JSONUtil.toBean(jsonStr, AbstractMap.SimpleEntry.class);
+
+ Assertions.assertEquals(entry, entry2);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void simpleEntryTest2() {
+ final AbstractMap.SimpleEntry entry = new AbstractMap.SimpleEntry<>("a", "b");
+ final String jsonStr = JSONUtil.toJsonStr(entry);
+ final MutableEntry entry2 = JSONUtil.toBean(jsonStr, MutableEntry.class);
+
+ Assertions.assertEquals(entry, entry2);
+ }
+}