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等。通用逻辑包括: + * * * @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 + * + * + * @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的类型,实现: + * * * @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); + } +}