diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanDesc.java index 5e88a6804..9c99619ed 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanDesc.java @@ -38,6 +38,54 @@ public interface BeanDesc extends Serializable { */ Map getPropMap(final boolean ignoreCase); + /** + * 获取Bean属性数量 + * + * @return 字段数量 + */ + default int size(){ + return getPropMap(false).size(); + } + + /** + * 是否为空 + * + * @return 是否为空 + */ + default boolean isEmpty(){ + return size() == 0; + } + + /** + * 是否有可读字段,即有getter方法或public字段 + * + * @param checkTransient 是否检查transient字段,true表示检查,false表示不检查 + * @return 是否有可读字段 + */ + default boolean isReadable(final boolean checkTransient){ + for (final Map.Entry entry : getPropMap(false).entrySet()) { + if (entry.getValue().isReadable(checkTransient)) { + return true; + } + } + return false; + } + + /** + * 是否有可写字段,即有setter方法或public字段 + * + * @param checkTransient 是否检查transient字段,true表示检查,false表示不检查 + * @return 是否有可写字段 + */ + default boolean isWritable(final boolean checkTransient){ + for (final Map.Entry entry : getPropMap(false).entrySet()) { + if (entry.getValue().isWritable(checkTransient)) { + return true; + } + } + return false; + } + /** * 获取字段属性列表 * diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java index 0b87b9f7b..d63f55082 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java @@ -311,7 +311,11 @@ public class TypeUtil { * @return {@link ParameterizedType} * @since 4.5.2 */ - public static ParameterizedType toParameterizedType(final Type type, final int interfaceIndex) { + public static ParameterizedType toParameterizedType(Type type, final int interfaceIndex) { + if(type instanceof TypeReference){ + type = ((TypeReference) type).getType(); + } + if (type instanceof ParameterizedType) { return (ParameterizedType) type; } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java index d8c6fc5bb..b7488521a 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java @@ -18,9 +18,7 @@ package org.dromara.hutool.json; import org.dromara.hutool.core.bean.path.BeanPath; import org.dromara.hutool.core.lang.mutable.MutableEntry; -import org.dromara.hutool.json.serializer.JSONDeserializer; import org.dromara.hutool.json.serializer.JSONMapper; -import org.dromara.hutool.json.serializer.TypeAdapterManager; import org.dromara.hutool.json.support.JSONNodeBeanFactory; import org.dromara.hutool.json.writer.JSONWriter; @@ -107,11 +105,70 @@ public interface JSON extends Serializable { * persons[3] * person.friends[5].name * + *

+ * 获取表达式对应值后转换为对应类型的值 * + * @param 返回值类型 * @param expression 表达式 * @return 对象 * @see BeanPath#getValue(Object) - * @since 4.0.6 + */ + default T getObjByPath(final String expression) { + return getByPath(expression, Object.class); + } + + /** + * 通过表达式获取JSON中嵌套的对象
+ *

    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ *

+ * 获取表达式对应值后转换为对应类型的值 + * + * @param 返回值类型 + * @param expression 表达式 + * @param resultType 返回值类型 + * @return 对象 + * @see BeanPath#getValue(Object) + */ + default T getByPath(final String expression, final Type resultType) { + final JSON json = getByPath(expression); + if (null == json) { + return null; + } + + return JSONMapper.of(config(), null).toBean(json, resultType); + } + + /** + * 通过表达式获取JSON中嵌套的JSON对象
+ *

    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param expression 表达式 + * @return JSON对象 + * @see BeanPath#getValue(Object) */ default JSON getByPath(final String expression) { return (JSON) BeanPath.of(expression).getValue(this); @@ -142,42 +199,6 @@ public interface JSON extends Serializable { BeanPath.of(expression, new JSONNodeBeanFactory(config())).setValue(this, value); } - /** - * 通过表达式获取JSON中嵌套的对象
- *
    - *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. - *
  3. []表达式,可以获取集合等对象中对应index的值
  4. - *
- *

- * 表达式栗子: - * - *

-	 * persion
-	 * persion.name
-	 * persons[3]
-	 * person.friends[5].name
-	 * 
- *

- * 获取表达式对应值后转换为对应类型的值 - * - * @param 返回值类型 - * @param expression 表达式 - * @param resultType 返回值类型 - * @return 对象 - * @see BeanPath#getValue(Object) - * @since 4.0.6 - */ - @SuppressWarnings("unchecked") - default T getByPath(final String expression, final Type resultType) { - final JSON json = getByPath(expression); - if (null == json) { - return null; - } - - final JSONDeserializer deserializer = TypeAdapterManager.getInstance().getDeserializer(json, resultType); - return (T) deserializer.deserialize(json, resultType); - } - /** * 格式化打印JSON,缩进为4个空格 * diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSONArray.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSONArray.java index 4460613d2..0df65ed7f 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSONArray.java @@ -18,12 +18,12 @@ package org.dromara.hutool.json; import org.dromara.hutool.core.collection.CollUtil; import org.dromara.hutool.core.collection.ListWrapper; -import org.dromara.hutool.core.convert.ConvertUtil; -import org.dromara.hutool.core.convert.impl.ArrayConverter; import org.dromara.hutool.core.lang.Validator; import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.json.serializer.JSONMapper; +import org.dromara.hutool.json.serializer.impl.ArrayTypeAdapter; +import org.dromara.hutool.json.serializer.impl.IterTypeAdapter; import org.dromara.hutool.json.writer.JSONWriter; import java.util.*; @@ -222,7 +222,7 @@ public class JSONArray extends ListWrapper implements JSON, JSONGetter T[] toArray(final T[] a) { - return (T[]) ArrayConverter.INSTANCE.convert(a.getClass().getComponentType(), this); + return (T[]) ArrayTypeAdapter.INSTANCE.deserialize(this, a.getClass().getComponentType()); } /** @@ -232,7 +232,7 @@ public class JSONArray extends ListWrapper implements JSON, JSONGetter arrayClass) { - return ArrayConverter.INSTANCE.convert(arrayClass, this); + return ArrayTypeAdapter.INSTANCE.deserialize(this, arrayClass.getComponentType()); } /** @@ -243,8 +243,9 @@ public class JSONArray extends ListWrapper implements JSON, JSONGetter List toList(final Class elementType) { - return ConvertUtil.toList(elementType, this); + return (List) IterTypeAdapter.INSTANCE.deserialize(this, ArrayList.class, elementType); } /** 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 79419d39b..609b0fca8 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 @@ -161,47 +161,57 @@ public class JSONUtil { if (obj instanceof byte[]) { obj = new ByteArrayInputStream((byte[]) obj); } - return (JSONObject) parse(obj, config, predicate); + + final JSONMapper jsonMapper = JSONMapper.of(config, predicate); + if (obj instanceof CharSequence) { + return (JSONObject) jsonMapper.map((CharSequence) obj); + } + return jsonMapper.mapObj(obj); } /** * JSON字符串转JSONArray * - * @param arrayOrCollection 数组或集合对象 + * @param obj 数组或集合对象或字符串等 * @return JSONArray * @since 3.0.8 */ - public static JSONArray parseArray(final Object arrayOrCollection) { - return parseArray(arrayOrCollection, null); + public static JSONArray parseArray(final Object obj) { + return parseArray(obj, null); } /** * JSON字符串转JSONArray * - * @param arrayOrCollection 数组或集合对象 + * @param obj 数组或集合对象 * @param config JSON配置 * @return JSONArray * @since 5.3.1 */ - public static JSONArray parseArray(final Object arrayOrCollection, final JSONConfig config) { - return parseArray(arrayOrCollection, config, null); + public static JSONArray parseArray(final Object obj, final JSONConfig config) { + return parseArray(obj, config, null); } /** * JSON字符串转JSONArray * - * @param arrayOrCollection 数组或集合对象 + * @param obj 数组或集合对象 * @param config JSON配置 * @param predicate index和值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@link Predicate#test(Object)}为{@code true}保留 * @return JSONArray * @since 5.3.1 */ - public static JSONArray parseArray(final Object arrayOrCollection, final JSONConfig config, final Predicate> predicate) { - if (arrayOrCollection instanceof JSONObject) { + public static JSONArray parseArray(final Object obj, final JSONConfig config, final Predicate> predicate) { + if (obj instanceof JSONObject) { final JSONMapper jsonMapper = JSONMapper.of(config, predicate); - return jsonMapper.mapFromJSONObject((JSONObject) arrayOrCollection); + return jsonMapper.mapFromJSONObject((JSONObject) obj); } - return (JSONArray) parse(arrayOrCollection, config, predicate); + + final JSONMapper jsonMapper = JSONMapper.of(config, predicate); + if (obj instanceof CharSequence) { + return (JSONArray) jsonMapper.map((CharSequence) obj); + } + return jsonMapper.mapArray(obj); } /** @@ -270,7 +280,7 @@ public class JSONUtil { } // endregion - // -------------------------------------------------------------------- Read start + // region ----- read /** * 读取JSON @@ -307,9 +317,9 @@ public class JSONUtil { public static JSONArray readJSONArray(final File file, final Charset charset) throws IORuntimeException { return FileUtil.read(file, charset, JSONUtil::parseArray); } - // -------------------------------------------------------------------- Read end + // endregion - // -------------------------------------------------------------------- toString start + // region ----- toJsonStr /** * 转换为格式化后的JSON字符串 @@ -380,9 +390,9 @@ public class JSONUtil { public static JSONObject xmlToJson(final String xml) { return JSONXMLUtil.toJSONObject(xml); } - // -------------------------------------------------------------------- toString end + // endregion - // -------------------------------------------------------------------- toBean start + // region ----- toBean /** * 转为实体类对象 @@ -398,20 +408,6 @@ public class JSONUtil { return toBean(json, (Type) clazz); } - /** - * 转为实体类对象 - * - * @param Bean类型 - * @param json JSONObject - * @param typeReference {@link TypeReference}类型参考子类,可以获取其泛型参数中的Type类型 - * @return 实体类对象 - * @since 4.6.2 - */ - public static T toBean(final Object json, final TypeReference typeReference) { - Assert.notNull(typeReference); - return toBean(json, typeReference.getType()); - } - /** * 转为实体类对象 * @@ -445,8 +441,9 @@ public class JSONUtil { } return json.toBean(type); } - // -------------------------------------------------------------------- toBean end + // endregion + // region ----- toList /** * 将JSONArray字符串转换为Bean的List,默认为ArrayList * @@ -472,6 +469,9 @@ public class JSONUtil { public static List toList(final JSONArray jsonArray, final Class elementType) { return null == jsonArray ? null : jsonArray.toList(elementType); } + // endregion + + // region ----- getByPath /** * 通过表达式获取JSON中嵌套的对象
@@ -489,17 +489,45 @@ public class JSONUtil { * person.friends[5].name * * + * @param 值类型 * @param json {@link JSON} * @param expression 表达式 * @return 对象 * @see JSON#getByPath(String) */ - public static JSON getByPath(final JSON json, final String expression) { + public static T getObjByPath(final JSON json, final String expression) { + return getByPath(json, expression, Object.class); + } + + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param 值类型 + * @param json {@link JSON} + * @param expression 表达式 + * @param type 结果类型 + * @return 对象 + * @see JSON#getByPath(String) + */ + public static T getByPath(final JSON json, final String expression, final Type type) { if ((null == json || StrUtil.isBlank(expression))) { return null; } - return json.getByPath(expression); + return json.getByPath(expression, type); } /** @@ -539,6 +567,36 @@ public class JSONUtil { return (T) json.getByPath(expression); } + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param json {@link JSON} + * @param expression 表达式 + * @return 对象 + * @see JSON#getByPath(String) + */ + public static JSON getByPath(final JSON json, final String expression) { + if ((null == json || StrUtil.isBlank(expression))) { + return null; + } + + return json.getByPath(expression); + } + // endregion + /** * 格式化JSON字符串,此方法并不严格检查JSON的格式正确与否 * @@ -567,6 +625,7 @@ public class JSONUtil { return json.isEmpty(); } + // region ----- isType /** * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 * @@ -605,4 +664,5 @@ public class JSONUtil { } return StrUtil.isWrap(StrUtil.trim(str), '[', ']'); } + // endregion } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/engine/jackson/JacksonEngine.java b/hutool-json/src/main/java/org/dromara/hutool/json/engine/jackson/JacksonEngine.java index 1fd4dd2ec..e1f2c9506 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/engine/jackson/JacksonEngine.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/engine/jackson/JacksonEngine.java @@ -116,6 +116,9 @@ public class JacksonEngine extends AbstractJSONEngine implements Wrapper + * 此对象为在Mapper时预定义的对象,用于指定序列化的JSON类型 * * @return JSON对象 */ diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/JSONMapper.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/JSONMapper.java index 624091b5d..892b550d5 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/JSONMapper.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/JSONMapper.java @@ -109,7 +109,7 @@ public class JSONMapper implements Serializable { if (json instanceof JSONPrimitive) { return (T) ((JSONPrimitive) json).getValue(); } - return (T) this; + return (T) json; } JSONDeserializer deserializer = null; @@ -149,7 +149,7 @@ public class JSONMapper implements Serializable { public JSONArray mapFromJSONObject(final JSONObject jsonObject) { final JSONArray array = JSONUtil.ofArray(jsonConfig); for (final Map.Entry entry : jsonObject) { - array.add(entry.getValue()); + array.set(entry); } return array; } @@ -163,6 +163,11 @@ public class JSONMapper implements Serializable { */ public JSON map(final CharSequence source) { final String jsonStr = StrUtil.trim(source); + if(StrUtil.isEmpty(jsonStr)){ + // https://www.rfc-editor.org/rfc/rfc8259#section-7 + // 未被包装的空串理解为null + return null; + } if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML final JSONObject jsonObject = JSONUtil.ofObj(jsonConfig); @@ -177,17 +182,65 @@ public class JSONMapper implements Serializable { * 在需要的时候转换映射对象
* 包装包括: *
    - *
  • array or collection =》 JSONArray
  • - *
  • map =》 JSONObject
  • - *
  • standard property (Double, String, et al) =》 原对象
  • - *
  • 来自于java包 =》 字符串
  • - *
  • 其它 =》 尝试包装为JSONObject,否则返回{@code null}
  • + *
  • array or collection =》 JSONArray
  • + *
  • map =》 JSONObject
  • + *
  • standard property (Double, String, et al) =》 原对象
  • + *
  • 其它 =》 尝试包装为JSONObject,否则返回{@code null}
  • *
* * @param obj 被映射的对象 * @return 映射后的值,null表示此值需被忽略 */ - public JSON map(Object obj) { + public JSON map(final Object obj) { + return mapTo(obj, null); + } + + /** + * 在需要的时候转换映射对象
+ * 包装包括: + *
    + *
  • map =》 JSONObject
  • + *
  • 其它 =》 尝试包装为JSONObject,否则返回{@code null}
  • + *
+ * + * @param obj 被映射的对象 + * @return 映射后的值,null表示此值需被忽略 + */ + public JSONObject mapObj(final Object obj) { + return mapTo(obj, JSONUtil.ofObj(jsonConfig)); + } + + /** + * 在需要的时候转换映射对象
+ * 包装包括: + *
    + *
  • array or collection =》 JSONArray
  • + *
+ * + * @param obj 被映射的对象 + * @return 映射后的值,null表示此值需被忽略 + */ + public JSONArray mapArray(final Object obj) { + return mapTo(obj, JSONUtil.ofArray(jsonConfig)); + } + + /** + * 在需要的时候转换映射对象
+ * 包装包括: + *
    + *
  • array or collection =》 JSONArray
  • + *
  • map =》 JSONObject
  • + *
  • standard property (Double, String, et al) =》 原对象
  • + *
  • 其它 =》 尝试包装为JSONObject,否则返回{@code null}
  • + *
+ * + * @param obj 被映射的对象 + * @param json 被映射的到的对象,{@code null}表示自动识别 + * @param JSON类型 + * @return 映射后的值,null表示此值需被忽略 + */ + @SuppressWarnings({"ReassignedVariable", "unchecked"}) + private T mapTo(Object obj, final T json) { if (null == obj) { return null; } @@ -204,8 +257,15 @@ public class JSONMapper implements Serializable { } } + // JSON对象如果与预期结果类型一致,则直接返回 if (obj instanceof JSON) { - return (JSON) obj; + if (null != json) { + if (obj.getClass() == json.getClass()) { + return (T) obj; + } + } else { + return (T) obj; + } } final Class clazz = obj.getClass(); @@ -226,14 +286,25 @@ public class JSONMapper implements Serializable { throw new JSONException("No deserializer for type: " + obj.getClass()); } + final JSON result; try { - return serializer.serialize(obj, new SimpleJSONContext(null, this.jsonConfig)); + result = serializer.serialize(obj, new SimpleJSONContext(json, this.jsonConfig)); } catch (final Exception e) { if (ignoreError) { return null; } throw e; } + + if(null == json || result.getClass() == json.getClass()){ + return (T) result; + } + + if(ignoreError){ + return null; + } + throw new JSONException("JSON type not match, expect: {}, actual: {}", + json.getClass().getName(), result.getClass().getName()); } /** diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/TypeAdapterManager.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/TypeAdapterManager.java index 60a663077..963ba75af 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/TypeAdapterManager.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/TypeAdapterManager.java @@ -189,7 +189,8 @@ public class TypeAdapterManager { } } - throw new JSONException("No serializer for type: " + type); + // 此处返回null,错误处理在mapper中 + return null; } /** diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java index 79d61c568..de6fa5092 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java @@ -67,9 +67,19 @@ public class ArrayTypeAdapter implements MatcherJSONSerializer, MatcherJ @Override public Object deserialize(final JSON json, final Type deserializeType) { - final int size = json.size(); final Class componentType = TypeUtil.getClass(deserializeType).getComponentType(); - final Object result = Array.newInstance(componentType, size); + return deserialize(json, componentType); + } + + /** + * 反序列化 + * + * @param json JSON对象 + * @param componentType 组件类型 + * @return 数组 + */ + public Object deserialize(final JSON json, final Class componentType) { + final Object result = Array.newInstance(componentType, json.size()); if (json instanceof JSONObject) { fill((JSONObject) json, result, componentType); } else { @@ -87,10 +97,12 @@ public class ArrayTypeAdapter implements MatcherJSONSerializer, MatcherJ */ private JSON serializeBytes(final byte[] bytes, final JSONContext context) { final JSONConfig config = context.config(); - switch (bytes[0]) { - case '{': - case '[': - return JSONParser.of(new JSONTokener(IoUtil.toStream(bytes)), config).parse(); + if(ArrayUtil.isNotEmpty(bytes)){ + switch (bytes[0]) { + case '{': + case '[': + return JSONParser.of(new JSONTokener(IoUtil.toStream(bytes)), config).parse(); + } } // https://github.com/dromara/hutool/issues/2369 diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/BeanTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/BeanTypeAdapter.java index db695bebe..92f879940 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/BeanTypeAdapter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/BeanTypeAdapter.java @@ -16,6 +16,7 @@ package org.dromara.hutool.json.serializer.impl; +import org.dromara.hutool.core.bean.BeanDesc; import org.dromara.hutool.core.bean.BeanUtil; import org.dromara.hutool.core.bean.copier.BeanToMapCopier; import org.dromara.hutool.core.bean.copier.ValueProviderToBeanCopier; @@ -25,11 +26,12 @@ import org.dromara.hutool.core.reflect.TypeUtil; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.json.InternalJSONUtil; import org.dromara.hutool.json.JSON; +import org.dromara.hutool.json.JSONConfig; import org.dromara.hutool.json.JSONObject; -import org.dromara.hutool.json.support.JSONObjectValueProvider; import org.dromara.hutool.json.serializer.JSONContext; import org.dromara.hutool.json.serializer.MatcherJSONDeserializer; import org.dromara.hutool.json.serializer.MatcherJSONSerializer; +import org.dromara.hutool.json.support.JSONObjectValueProvider; import java.lang.reflect.Type; @@ -49,13 +51,23 @@ public class BeanTypeAdapter implements MatcherJSONSerializer, MatcherJS @Override public boolean match(final Object bean, final JSONContext context) { final JSON contextJson = ObjUtil.apply(context, JSONContext::getContextJson); - return BeanUtil.isReadableBean(bean.getClass()) + final BeanDesc beanDesc = BeanUtil.getBeanDesc(bean.getClass()); + if(beanDesc.isEmpty()){ + // 空Bean按照Bean对待 + return true; + } + + final boolean isTransparent = ObjUtil.defaultIfNull( + ObjUtil.apply(contextJson, JSON::config), JSONConfig::isTransientSupport, true); + return beanDesc.isReadable(isTransparent) && (null == contextJson || contextJson instanceof JSONObject); } @Override public boolean match(final JSON json, final Type deserializeType) { - return json instanceof JSONObject && BeanUtil.isWritableBean(TypeUtil.getClass(deserializeType)); + return json instanceof JSONObject && + // 空对象转目标对象不限制目标是否可写 + (json.isEmpty() || BeanUtil.isWritableBean(TypeUtil.getClass(deserializeType))); } @Override @@ -72,9 +84,14 @@ public class BeanTypeAdapter implements MatcherJSONSerializer, MatcherJS @Override public Object deserialize(final JSON json, final Type deserializeType) { + final Object target = ConstructorUtil.newInstanceIfPossible(TypeUtil.getClass(deserializeType)); + if(json.isEmpty()){ + //issue#3649,对于空对象转目标对象,直接实例化一个空对象 + return target; + } final Copier copier = new ValueProviderToBeanCopier<>( new JSONObjectValueProvider((JSONObject) json), - ConstructorUtil.newInstanceIfPossible(TypeUtil.getClass(deserializeType)), + target, deserializeType, InternalJSONUtil.toCopyOptions(json.config()) ); diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/IterTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/IterTypeAdapter.java index 35cd767a7..be0097b80 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/IterTypeAdapter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/IterTypeAdapter.java @@ -29,7 +29,6 @@ import org.dromara.hutool.json.serializer.MatcherJSONSerializer; import java.lang.reflect.Type; import java.util.Collection; import java.util.Iterator; -import java.util.Map; /** * Iterator序列化器,将{@link Iterable}或{@link Iterator}转换为JSONArray @@ -45,7 +44,7 @@ public class IterTypeAdapter implements MatcherJSONSerializer, MatcherJS @Override public boolean match(final Object bean, final JSONContext context) { - if(bean instanceof MapWrapper){ + if (bean instanceof MapWrapper) { return false; } return bean instanceof Iterable || bean instanceof Iterator; @@ -77,10 +76,21 @@ public class IterTypeAdapter implements MatcherJSONSerializer, MatcherJS @Override public Object deserialize(final JSON json, final Type deserializeType) { - final Class rawType = TypeUtil.getClass(deserializeType); + final Class collectionClass = TypeUtil.getClass(deserializeType); final Type elementType = TypeUtil.getTypeArgument(deserializeType); - final Collection result = CollUtil.create(rawType, TypeUtil.getClass(elementType)); + return deserialize(json, collectionClass, elementType); + } + /** + * 反序列化 + * + * @param json JSON + * @param collectionClass 集合类型 + * @param elementType 元素类型 + * @return 反序列化后的集合对象 + */ + public Object deserialize(final JSON json, final Class collectionClass, final Type elementType) { + final Collection result = CollUtil.create(collectionClass, TypeUtil.getClass(elementType)); if (json instanceof JSONObject) { fill((JSONObject) json, result, elementType); @@ -116,9 +126,9 @@ public class IterTypeAdapter implements MatcherJSONSerializer, MatcherJS * @param elementType 元素类型 */ private void fill(final JSONObject json, final Collection result, final Type elementType) { - for (final Map.Entry entry : json) { - result.add(entry.getValue().toBean(elementType)); - } + json.forEach((key, value)->{ + result.add(null == value ? null : value.toBean(elementType)); + }); } /** @@ -129,8 +139,8 @@ public class IterTypeAdapter implements MatcherJSONSerializer, MatcherJS * @param elementType 元素类型 */ private void fill(final JSONArray json, final Collection result, final Type elementType) { - for (final JSON element : json) { - result.add(element.toBean(elementType)); - } + json.forEach((element)->{ + result.add(null == element ? null : element.toBean(elementType)); + }); } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TemporalTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TemporalTypeAdapter.java index 65b472770..2e83897fa 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TemporalTypeAdapter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TemporalTypeAdapter.java @@ -73,6 +73,7 @@ public class TemporalTypeAdapter implements MatcherJSONSerializer paramList = JSONUtil.toList(array, Object.class); + Assertions.assertEquals(JSONArray.class, paramList.get(1).getClass()); + } + @Test public void bytesTest() { final Object[] paramArray = new Object[]{1, new byte[]{10, 11}, "报表.xlsx"}; final String paramsStr = JSONUtil.toJsonStr(paramArray); Assertions.assertEquals("[1,[10,11],\"报表.xlsx\"]", paramsStr); - final List paramList = JSONUtil.toList(paramsStr, Object.class); + final JSONArray array = JSONUtil.parseArray(paramsStr); + final List paramList = JSONUtil.toList(array, Object.class); + Assertions.assertEquals(JSONArray.class, paramList.get(1).getClass()); final String paramBytesStr = JSONUtil.toJsonStr(paramList.get(1)); Assertions.assertEquals("[10,11]", paramBytesStr); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3051Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3051Test.java index 86ceeaa25..777f8f302 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3051Test.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3051Test.java @@ -24,6 +24,8 @@ public class Issue3051Test { @Test public void parseTest() { + // 空Bean按照Bean对待,转为空对象 + // 逻辑见:BeanTypeAdapter final JSONObject jsonObject = JSONUtil.parseObj(new EmptyBean(), JSONConfig.of().setIgnoreError(true)); @@ -32,10 +34,8 @@ public class Issue3051Test { @Test public void parseTest2() { - Assertions.assertThrows(JSONException.class, ()->{ - final JSONObject jsonObject = JSONUtil.parseObj(new EmptyBean()); - Assertions.assertEquals("{}", jsonObject.toString()); - }); + final JSONObject jsonObject = JSONUtil.parseObj(new EmptyBean()); + Assertions.assertEquals("{}", jsonObject.toString()); } @Data diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3139Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3139Test.java index 66807e197..fc54149bc 100755 --- a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3139Test.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3139Test.java @@ -17,7 +17,9 @@ package org.dromara.hutool.json; import lombok.Data; -import org.dromara.hutool.core.xml.XmlUtil; +import org.dromara.hutool.core.collection.ListUtil; +import org.dromara.hutool.json.serializer.JSONDeserializer; +import org.dromara.hutool.json.serializer.TypeAdapterManager; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -33,8 +35,21 @@ public class Issue3139Test { " \n" + ""; - final JSONObject jsonObject = XmlUtil.xmlToBean(XmlUtil.parseXml(xml).getDocumentElement(), JSONObject.class); - final R bean = jsonObject.toBean(R.class); + final JSONObject jsonObject = JSONUtil.parseObj(xml); + Assertions.assertEquals("{\"r\":{\"c\":{\"s\":1,\"p\":\"str\"}}}", jsonObject.toString()); + + final JSONObject r = jsonObject.getJSONObject("r"); + Assertions.assertEquals("{\"c\":{\"s\":1,\"p\":\"str\"}}", r.toString()); + + // R中的c为List,但是JSON中为JSONObject,默认会遍历键值对,此处需要自定义序列化 + TypeAdapterManager.getInstance().register(R.class, (JSONDeserializer) (json, deserializeType) -> { + final R r1 = new R(); + // c作为一个独立对象放入List中 + r1.setC(ListUtil.of((C) json.asJSONObject().get("c", C.class))); + return r1; + }); + + final R bean = r.toBean(R.class); Assertions.assertNotNull(bean); final List c = bean.getC(); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3649Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3649Test.java index 92cc4bb6e..92d028c34 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/Issue3649Test.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/Issue3649Test.java @@ -20,13 +20,25 @@ import lombok.Data; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.List; + public class Issue3649Test { @Test void toEmptyBeanTest() { + //issue#3649,对于空对象转目标对象,直接实例化一个空对象 + // 逻辑见:BeanTypeAdapter final Object bean = JSONUtil.toBean("{}", JSONConfig.of().setIgnoreError(false), EmptyBean.class); Assertions.assertEquals(new EmptyBean(), bean); } + @Test + void toEmptyListTest() { + final List bean = JSONUtil.toBean("[]", JSONConfig.of().setIgnoreError(false), List.class); + Assertions.assertNotNull(bean); + Assertions.assertTrue(bean.isEmpty()); + } + @Data - public static class EmptyBean {} + public static class EmptyBean { + } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONArrayTest.java index 2f634dab9..c8e7dcccd 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONArrayTest.java @@ -172,7 +172,11 @@ public class JSONArrayTest { final JSONArray array = JSONUtil.parseArray(jsonStr); //noinspection SuspiciousToArrayCall - final Exam[] list = array.toArray(new Exam[0]); + Exam[] list = array.toArray(new Exam[0]); + Assertions.assertNotEquals(0, list.length); + Assertions.assertSame(Exam.class, list[0].getClass()); + + list = (Exam[]) array.toArray(Exam[].class); Assertions.assertNotEquals(0, list.length); Assertions.assertSame(Exam.class, list[0].getClass()); } @@ -233,8 +237,8 @@ public class JSONArrayTest { public void getByPathTest() { final String jsonStr = "[{\"id\": \"1\",\"name\": \"a\"},{\"id\": \"2\",\"name\": \"b\"}]"; final JSONArray jsonArray = JSONUtil.parseArray(jsonStr); - assertEquals("b", jsonArray.getByPath("[1].name")); - assertEquals("b", JSONUtil.getByPath(jsonArray, "[1].name")); + assertEquals("b", jsonArray.getByPath("[1].name", Object.class)); + assertEquals("b", JSONUtil.getObjByPath(jsonArray, "[1].name")); } @Test @@ -256,7 +260,7 @@ public class JSONArrayTest { final JSONArray jsonArray = new JSONArray(); jsonArray.setValue(0, 1); assertEquals(1, jsonArray.size()); - assertEquals(1, jsonArray.get(0)); + assertEquals(1, jsonArray.getObj(0)); } private static Map buildMap(final String id, final String parentId, final String name) { diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONObjectTest.java index 0e3eed2bd..24a726713 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONObjectTest.java @@ -619,7 +619,7 @@ public class JSONObjectTest { @Test public void createJSONObjectTest() { - Assertions.assertThrows(ClassCastException.class, ()->{ + Assertions.assertThrows(JSONException.class, ()->{ // 集合类不支持转为JSONObject JSONUtil.parseObj(new JSONArray(), JSONConfig.of()); }); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONPathTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONPathTest.java index e1601c91c..51782b9c4 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONPathTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONPathTest.java @@ -30,16 +30,16 @@ public class JSONPathTest { @Test public void getByPathTest() { final String json = "[{\"id\":\"1\",\"name\":\"xingming\"},{\"id\":\"2\",\"name\":\"mingzi\"}]"; - Object value = JSONUtil.parseArray(json).getByPath("[0].name"); + Object value = JSONUtil.parseArray(json).getByPath("[0].name", Object.class); Assertions.assertEquals("xingming", value); - value = JSONUtil.parseArray(json).getByPath("[1].name"); + value = JSONUtil.parseArray(json).getByPath("[1].name", Object.class); Assertions.assertEquals("mingzi", value); } @Test public void getByPathTest2(){ final String str = "{'accountId':111}"; - final JSON json = (JSON) JSONUtil.parse(str); + final JSON json = JSONUtil.parse(str); final Long accountId = JSONUtil.getByPath(json, "$.accountId", 0L); Assertions.assertEquals(111L, accountId.longValue()); } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java index ace523c24..89185908c 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java @@ -102,20 +102,25 @@ public class JSONUtilTest { */ @Test public void parseNumberToJSONArrayTest() { - assertThrows(ClassCastException.class, () -> { + assertThrows(JSONException.class, () -> { final JSONArray json = JSONUtil.parseArray(123L); Assertions.assertNotNull(json); }); } /** - * 数字解析为JSONArray报错 + * 数字解析为JSONArray报错,忽略错误则返回null */ @Test public void parseNumberToJSONArrayTest2() { + Assertions.assertThrows(JSONException.class, ()->{ + JSONUtil.parseArray(123L, + JSONConfig.of().setIgnoreError(false)); + }); + final JSONArray json = JSONUtil.parseArray(123L, JSONConfig.of().setIgnoreError(true)); - Assertions.assertNotNull(json); + Assertions.assertNull(json); } /** @@ -135,7 +140,7 @@ public class JSONUtilTest { @Test public void parseNumberToJSONObjectTest2() { final JSONObject json = JSONUtil.parseObj(123L, JSONConfig.of().setIgnoreError(true)); - assertEquals(new JSONObject(), json); + assertNull(json); } @Test @@ -341,7 +346,7 @@ public class JSONUtilTest { public void testArrayEntity() { final String jsonStr = JSONUtil.toJsonStr(new ArrayEntity()); // a为空的bytes数组,按照空的流对待 - assertEquals("{\"b\":[0],\"c\":[],\"d\":[],\"e\":[]}", jsonStr); + assertEquals("{\"a\":[],\"b\":[0],\"c\":[],\"d\":[],\"e\":[]}", jsonStr); } @Data diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/Pr3067Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/Pr3067Test.java index c1ae59fa9..2b9666f81 100755 --- a/hutool-json/src/test/java/org/dromara/hutool/json/Pr3067Test.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/Pr3067Test.java @@ -26,9 +26,37 @@ import java.util.List; public class Pr3067Test { + final String jsonStr = "[{\"username\":\"a\",\"password\":\"a-password\"}, {\"username\":\"b\",\"password\":\"b-password\"}]"; + + @Test + void toListTest() { + final JSONArray array = JSONUtil.parseArray(jsonStr); + final List resultList = array.toList(TestUser.class); + Assertions.assertNotNull(resultList); + Assertions.assertEquals(2, resultList.size()); + Assertions.assertEquals("a", resultList.get(0).getUsername()); + Assertions.assertEquals("a-password", resultList.get(0).getPassword()); + Assertions.assertEquals("b", resultList.get(1).getUsername()); + Assertions.assertEquals("b-password", resultList.get(1).getPassword()); + } + + @Test + void toTypeReferenceTest() { + final JSONArray array = JSONUtil.parseArray(jsonStr); + final List resultList = array.toBean(new TypeReference>() {}); + + Assertions.assertNotNull(resultList); + Assertions.assertEquals(2, resultList.size()); + Assertions.assertEquals("a", resultList.get(0).getUsername()); + Assertions.assertEquals("a-password", resultList.get(0).getPassword()); + Assertions.assertEquals("b", resultList.get(1).getUsername()); + Assertions.assertEquals("b-password", resultList.get(1).getPassword()); + } + @Test public void getListByPathTest1() { final JSONObject json = JSONUtil.parseObj(ResourceUtil.readUtf8Str("test_json_path_001.json")); + final List resultList = json.getByPath("testUserList[1].testArray", new TypeReference>() {}); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java index de4056eac..44036acd0 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/engine/JSONEngineTest.java @@ -16,6 +16,7 @@ package org.dromara.hutool.json.engine; +import lombok.Data; import org.dromara.hutool.core.date.DateTime; import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.date.TimeUtil; @@ -49,6 +50,11 @@ public class JSONEngineTest { Arrays.stream(engineNames).forEach(this::assertWriteTimeZone); } + @Test + void writeEmptyBeanTest() { + Arrays.stream(engineNames).forEach(this::assertEmptyBeanToJson); + } + private void assertWriteDateFormat(final String engineName) { final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); @@ -96,4 +102,15 @@ public class JSONEngineTest { jsonString = engine.toJsonString(timeZone); Assertions.assertEquals("\"GMT+08:00\"", jsonString); } + + private void assertEmptyBeanToJson(final String engineName){ + final JSONEngine engine = JSONEngineFactory.createEngine(engineName); + final String jsonString = engine.toJsonString(new EmptyBean()); + Assertions.assertEquals("{}", jsonString); + } + + @Data + private static class EmptyBean{ + + } }