This commit is contained in:
Looly 2023-04-04 23:47:50 +08:00
parent 98d08dc00a
commit fafdbbd453
9 changed files with 284 additions and 67 deletions

View File

@ -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实现对应类型的专属逻辑<br>
* 转换器不会抛出转换异常转换失败时会返回{@code null}
* 转换器不会抛出转换异常转换失败时会返回{@code null}<br>
* 抽象转换器的默认逻辑不适用于有泛型参数的对象如MapCollectionEntry等通用逻辑包括
* <ul>
* <li>value为{@code null}时返回{@code null}</li>
* <li>目标类型是{@code null}或者{@link java.lang.reflect.TypeVariable}抛出{@link ConvertException}异常</li>
* <li>目标类型非class时抛出{@link IllegalArgumentException}</li>
* <li>目标类型为值的父类或同类直接强转返回</li>
* </ul>
*
* @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);
}

View File

@ -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;

View File

@ -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
* <ul>
* <li>{@link Map}</li>
* <li>{@link Map.Entry}</li>
* <li>带分隔符的字符串支持分隔符{@code :}{@code =}{@code ,}</li>
* <li>Bean包含{@code getKey}{@code getValue}方法</li>
* </ul>
*
* @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<CharSequence, CharSequence> 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)
);
}
}

View File

@ -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的类型实现
* <ul>
* <li>Map Mapkey和value类型自动转换</li>
* <li>Bean Map字段和字段值类型自动转换</li>
* </ul>
*
* @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)
));
}
}

View File

@ -13,6 +13,11 @@
/**
* 万能类型转换器以及各种类型转换的实现类其中Convert为转换器入口提供各种toXXX方法和convert方法
*
* <p>
* 转换器是典型的策略模式应用通过实现{@link org.dromara.hutool.core.convert.Converter} 接口
* 自定义转换策略Hutool提供了常用类型的转换策略
* </p>
*
* @author looly
*
*/

View File

@ -27,6 +27,19 @@ import java.util.Map;
public class MutableEntry<K, V> extends AbsEntry<K, V> implements Mutable<Map.Entry<K, V>>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建{@code MutableEntry}
*
* @param key
* @param value
* @param <K> 键类型
* @param <V> 值类型
* @return
*/
public static <K, V> MutableEntry<K, V> of(final K key, final V value) {
return new MutableEntry<>(key, value);
}
protected K key;
protected V value;

View File

@ -40,6 +40,7 @@ import java.util.List;
public class JSONUtil {
// -------------------------------------------------------------------- Pause start
/**
* 创建JSONObject
*
@ -169,7 +170,7 @@ public class JSONUtil {
* @return JSONJSONObject 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 <T> Bean类型
* @param jsonString JSON字符串
* @param beanClass 实体类对象
* @param <T> Bean类型
* @param json JSONObject
* @param clazz 实体类
* @return 实体类对象
* @since 3.1.2
* @since 4.6.2
*/
public static <T> T toBean(final String jsonString, final Class<T> beanClass) {
return toBean(parse(jsonString), beanClass);
}
/**
* JSON字符串转为实体类对象转换异常将被抛出<br>
* 通过{@link JSONConfig}可选是否忽略大小写忽略null等配置
*
* @param <T> Bean类型
* @param jsonString JSON字符串
* @param config JSON配置
* @param beanClass 实体类对象
* @return 实体类对象
* @since 5.8.0
*/
public static <T> T toBean(final String jsonString, final JSONConfig config, final Class<T> beanClass) {
return toBean(jsonString, config, (Type) beanClass);
}
/**
* JSON字符串转为实体类对象转换异常将被抛出<br>
* 通过{@link JSONConfig}可选是否忽略大小写忽略null等配置
*
* @param <T> Bean类型
* @param jsonString JSON字符串
* @param config JSON配置
* @param type Bean类型
* @return 实体类对象
* @throws JSONException 提供的JSON字符串不支持转Bean或字符串错误
*/
public static <T> T toBean(final String jsonString, final JSONConfig config, final Type type) throws JSONException {
return toBean(parse(jsonString, config), type);
public static <T> T toBean(final Object json, final Class<T> clazz) {
Assert.notNull(clazz);
return toBean(json, (Type)clazz);
}
/**
@ -391,12 +363,31 @@ public class JSONUtil {
* @since 4.3.2
*/
public static <T> T toBean(final Object json, final Type type) {
return toBean(json, null, type);
}
/**
* 转为实体类对象
*
* @param <T> Bean类型
* @param json JSONObject
* @param config JSON配置
* @param type 实体类对象类型
* @return 实体类对象
* @since 4.3.2
*/
public static <T> 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

View File

@ -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 {
* <pre>
* Collection
* Map
* Map.Entry
* 强转无需转换
* 数组
* </pre>
@ -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;

View File

@ -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<String, String> entry = MutableEntry.of("a", "b");
final String jsonStr = JSONUtil.toJsonStr(entry);
final MutableEntry<String, String> entry2 = JSONUtil.toBean(jsonStr, MutableEntry.class);
Assertions.assertEquals(entry, entry2);
}
@Test
void mutableEntryTest2() {
final MutableEntry<Integer, Integer> entry = MutableEntry.of(1, 2);
final String jsonStr = JSONUtil.toJsonStr(entry);
final MutableEntry<Integer, Integer> entry2 = JSONUtil.toBean(jsonStr,
new TypeReference<MutableEntry<Integer, Integer>>() {});
Assertions.assertEquals(entry, entry2);
}
@SuppressWarnings("unchecked")
@Test
void simpleEntryTest() {
final AbstractMap.SimpleEntry<String, String> entry = new AbstractMap.SimpleEntry<>("a", "b");
final String jsonStr = JSONUtil.toJsonStr(entry);
final AbstractMap.SimpleEntry<String, String> entry2 = JSONUtil.toBean(jsonStr, AbstractMap.SimpleEntry.class);
Assertions.assertEquals(entry, entry2);
}
@SuppressWarnings("unchecked")
@Test
void simpleEntryTest2() {
final AbstractMap.SimpleEntry<String, String> entry = new AbstractMap.SimpleEntry<>("a", "b");
final String jsonStr = JSONUtil.toJsonStr(entry);
final MutableEntry<String, String> entry2 = JSONUtil.toBean(jsonStr, MutableEntry.class);
Assertions.assertEquals(entry, entry2);
}
}