From d8baf834747c61502beb8b862e30f029c043ccbf Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 7 Apr 2020 11:19:46 +0800 Subject: [PATCH] fix bug --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/util/StrUtil.java | 15 +++- .../java/cn/hutool/json/InternalJSONUtil.java | 2 +- .../main/java/cn/hutool/json/JSONArray.java | 11 ++- .../main/java/cn/hutool/json/JSONGetter.java | 78 ++++++++++--------- .../main/java/cn/hutool/json/JSONObject.java | 25 +++--- .../main/java/cn/hutool/json/JSONTokener.java | 28 ++++--- .../src/main/java/cn/hutool/json/XML.java | 2 +- .../main/java/cn/hutool/json/XMLTokener.java | 44 ++++++----- .../java/cn/hutool/json/JSONObjectTest.java | 5 +- .../cn/hutool/json/test/bean/UserWithMap.java | 11 +-- 11 files changed, 124 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9966a05f6..e72855ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.2.6 (2020-04-01) +## 5.3.0 (2020-04-07) ### 新特性 * 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) @@ -25,6 +25,7 @@ * 【extra 】 修复SpringUtil使用devtools重启报错问题 * 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github) * 【extra 】 修复模板中无效引用的问题 +* 【extra 】 修复读取JSON文本配置未应用到子对象的问题(issue#818@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index f3f36f253..530d51d21 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2385,8 +2385,12 @@ public class StrUtil { } /** - * 将对象转为字符串
- * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * 将对象转为字符串 + * + *
+	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 2、对象数组会调用Arrays.toString方法
+	 * 
 	 *
 	 * @param obj         对象
 	 * @param charsetName 字符集
@@ -2397,8 +2401,11 @@ public class StrUtil {
 	}
 
 	/**
-	 * 将对象转为字符串
- * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * 将对象转为字符串 + *
+	 * 	 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 	 2、对象数组会调用Arrays.toString方法
+	 * 
 	 *
 	 * @param obj     对象
 	 * @param charset 字符集
diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
index 693d1c264..b3be2fee6 100644
--- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
+++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
@@ -196,7 +196,7 @@ final class InternalJSONUtil {
 			String segment = path[i];
 			JSONObject nextTarget = target.getJSONObject(segment);
 			if (nextTarget == null) {
-				nextTarget = new JSONObject();
+				nextTarget = new JSONObject(target.getConfig());
 				target.set(segment, nextTarget);
 			}
 			target = nextTarget;
diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
index 5a17702f7..da5abb041 100644
--- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
+++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
@@ -189,6 +189,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
 	}
 	// -------------------------------------------------------------------------------------------------------------------- Constructor start
 
+	@Override
+	public JSONConfig getConfig() {
+		return this.config;
+	}
+
 	/**
 	 * 设置转为字符串时的日期格式,默认为时间戳(null值)
 	 * 
@@ -295,7 +300,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
 		if (names == null || names.size() == 0 || this.size() == 0) {
 			return null;
 		}
-		JSONObject jo = new JSONObject();
+		final JSONObject jo = new JSONObject(this.config);
 		for (int i = 0; i < names.size(); i += 1) {
 			jo.set(names.getStr(i), this.getObj(i));
 		}
@@ -591,6 +596,8 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
 		} else if (source instanceof CharSequence) {
 			// JSON字符串
 			init((CharSequence) source);
+		}else if (source instanceof JSONTokener) {
+			init((JSONTokener) source);
 		} else {
 			Iterator iter;
 			if (source.getClass().isArray()) {// 数组
@@ -615,7 +622,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
 	 */
 	private void init(CharSequence source) {
 		if (null != source) {
-			init(new JSONTokener(StrUtil.trim(source)));
+			init(new JSONTokener(StrUtil.trim(source), this.config));
 		}
 	}
 
diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
index 547e15c99..e05c03d75 100644
--- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
+++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
@@ -5,12 +5,20 @@ import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter;
 
 /**
  * 用于JSON的Getter类,提供各种类型的Getter方法
- * @author Looly
  *
  * @param  Key类型
+ * @author Looly
  */
-public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
-	
+public interface JSONGetter extends OptNullBasicTypeFromObjectGetter {
+
+	/**
+	 * 获取JSON配置
+	 *
+	 * @return {@link JSONConfig}
+	 * @since 5.3.0
+	 */
+	JSONConfig getConfig();
+
 	/**
 	 * key对应值是否为null或无此key
 	 *
@@ -20,10 +28,10 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
 	default boolean isNull(K key) {
 		return JSONNull.NULL.equals(this.getObj(key));
 	}
-	
+
 	/**
 	 * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n"
-	 * 
+	 *
 	 * @param key 键
 	 * @return 字符串类型值
 	 * @since 4.2.2
@@ -31,11 +39,11 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
 	default String getStrEscaped(K key) {
 		return getStrEscaped(key, null);
 	}
-	
+
 	/**
 	 * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n"
-	 * 
-	 * @param key 键
+	 *
+	 * @param key          键
 	 * @param defaultValue 默认值
 	 * @return 字符串类型值
 	 * @since 4.2.2
@@ -43,51 +51,51 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
 	default String getStrEscaped(K key, String defaultValue) {
 		return JSONUtil.escape(getStr(key, defaultValue));
 	}
-	
+
 	/**
 	 * 获得JSONArray对象
* 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONArray) { + + if (object instanceof JSONArray) { return (JSONArray) object; } - return new JSONArray(object); + return new JSONArray(object, getConfig()); } /** * 获得JSONObject对象
* 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONObject) { + + if (object instanceof JSONObject) { return (JSONObject) object; } - return new JSONObject(object); + return new JSONObject(object, getConfig()); } - + /** * 从JSON中直接获取Bean对象
* 先获取JSONObject对象,然后转为Bean对象 - * - * @param Bean类型 - * @param key KEY + * + * @param Bean类型 + * @param key KEY * @param beanType Bean类型 * @return Bean对象,如果值为null或者非JSONObject类型,返回null * @since 3.1.1 @@ -96,36 +104,36 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ final JSONObject obj = getJSONObject(key); return (null == obj) ? null : obj.toBean(beanType); } - + /** * 获取指定类型的对象
* 转换失败或抛出异常 - * - * @param 获取的对象类型 - * @param key 键 + * + * @param 获取的对象类型 + * @param key 键 * @param type 获取对象类型 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type) throws ConvertException{ + default T get(K key, Class type) throws ConvertException { return get(key, type, false); } - + /** * 获取指定类型的对象 - * - * @param 获取的对象类型 - * @param key 键 - * @param type 获取对象类型 + * + * @param 获取的对象类型 + * @param key 键 + * @param type 获取对象类型 * @param ignoreError 是否跳过转换失败的对象或值 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type, boolean ignoreError) throws ConvertException{ + default T get(K key, Class type, boolean ignoreError) throws ConvertException { final Object value = this.getObj(key); - if(null == value){ + if (null == value) { return null; } return JSONConverter.jsonConvert(type, value, ignoreError); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 655d31454..cd62146c3 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -7,6 +7,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -22,7 +23,6 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -103,14 +103,17 @@ public class JSONObject implements JSON, JSONGetter, Map * 构造 * * @param capacity 初始大小 - * @param config JSON配置项 + * @param config JSON配置项,null表示默认配置 * @since 4.1.19 */ public JSONObject(int capacity, JSONConfig config) { + if(null == config){ + config = JSONConfig.create(); + } if (config.isIgnoreCase()) { this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); } else { - this.rawHashMap = config.isOrder() ? new LinkedHashMap<>(capacity) : new HashMap<>(capacity); + this.rawHashMap = MapUtil.newHashMap(config.isOrder()); } this.config = config; } @@ -241,12 +244,7 @@ public class JSONObject implements JSON, JSONGetter, Map // -------------------------------------------------------------------------------------------------------------------- Constructor end - /** - * 获取JSON配置 - * - * @return {@link JSONConfig} - * @since 4.3.1 - */ + @Override public JSONConfig getConfig() { return this.config; } @@ -493,19 +491,16 @@ public class JSONObject implements JSON, JSONGetter, Map rawHashMap.clear(); } - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return this.rawHashMap.keySet(); } - @SuppressWarnings("NullableProblems") @Override public Collection values() { return rawHashMap.values(); } - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { return rawHashMap.entrySet(); @@ -661,7 +656,7 @@ public class JSONObject implements JSON, JSONGetter, Map * * @param source JavaBean或者Map对象或者String */ - @SuppressWarnings({"rawtypes", "unchecked", "StatementWithEmptyBody"}) + @SuppressWarnings({"rawtypes", "unchecked"}) private void init(Object source) { if (null == source) { return; @@ -696,7 +691,7 @@ public class JSONObject implements JSON, JSONGetter, Map * @param source JSON字符串 */ private void init(CharSequence source) { - init(new JSONTokener(StrUtil.trim(source))); + init(new JSONTokener(StrUtil.trim(source), this.config)); } /** @@ -711,7 +706,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); } - for (;;) { + while (true) { c = x.nextClean(); switch (c) { case 0: diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 0cca017a5..43ee692b1 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -1,5 +1,7 @@ package cn.hutool.json; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -40,14 +42,20 @@ public class JSONTokener { */ private Reader reader; + /** + * JSON配置 + */ + private JSONConfig config; + // ------------------------------------------------------------------------------------ Constructor start /** * 从Reader中构建 * * @param reader Reader + * @param config JSON配置 */ - public JSONTokener(Reader reader) { + public JSONTokener(Reader reader, JSONConfig config) { this.reader = reader.markSupported() ? reader : new BufferedReader(reader); this.eof = false; this.usePrevious = false; @@ -61,18 +69,20 @@ public class JSONTokener { * 从InputStream中构建 * * @param inputStream InputStream + * @param config JSON配置 */ - public JSONTokener(InputStream inputStream) throws JSONException { - this(new InputStreamReader(inputStream)); + public JSONTokener(InputStream inputStream, JSONConfig config) throws JSONException { + this(new InputStreamReader(inputStream), config); } /** * 从字符串中构建 * - * @param s JSON字符串 + * @param s JSON字符串 + * @param config JSON配置 */ - public JSONTokener(String s) { - this(new StringReader(s)); + public JSONTokener(CharSequence s, JSONConfig config) { + this(new StringReader(StrUtil.str(s)), config); } // ------------------------------------------------------------------------------------ Constructor end @@ -318,10 +328,10 @@ public class JSONTokener { return this.nextString(c); case '{': this.back(); - return new JSONObject(this); + return new JSONObject(this, this.config); case '[': this.back(); - return new JSONArray(this); + return new JSONArray(this, this.config); } /* @@ -391,7 +401,7 @@ public class JSONTokener { * @return {@link JSONArray} */ public JSONArray toJSONArray() { - JSONArray jsonArray = new JSONArray(); + JSONArray jsonArray = new JSONArray(this.config); if (this.nextClean() != '[') { throw this.syntaxError("A JSONArray text must start with '['"); } diff --git a/hutool-json/src/main/java/cn/hutool/json/XML.java b/hutool-json/src/main/java/cn/hutool/json/XML.java index 08ccb810a..e2067ec55 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XML.java +++ b/hutool-json/src/main/java/cn/hutool/json/XML.java @@ -229,7 +229,7 @@ public class XML { */ public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string); + XMLTokener x = new XMLTokener(string, jo.getConfig()); while (x.more() && x.skipPast("<")) { parse(x, jo, null, keepStrings); } diff --git a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java index 79e79b29d..9ed2559f8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java @@ -2,18 +2,19 @@ package cn.hutool.json; /** * XML分析器,继承自JSONTokener,提供XML的语法分析 - * + * * @author JSON.org */ public class XMLTokener extends JSONTokener { /** - * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot. + * The table of entity values. + * It initially contains Character values for amp, apos, gt, lt, quot. */ public static final java.util.HashMap entity; static { - entity = new java.util.HashMap(8); + entity = new java.util.HashMap<>(8); entity.put("amp", XML.AMP); entity.put("apos", XML.APOS); entity.put("gt", XML.GT); @@ -23,16 +24,17 @@ public class XMLTokener extends JSONTokener { /** * Construct an XMLTokener from a string. - * - * @param s A source string. + * + * @param s A source string. + * @param config JSON配置 */ - public XMLTokener(String s) { - super(s); + public XMLTokener(CharSequence s, JSONConfig config) { + super(s, config); } /** * Get the text in the CDATA block. - * + * * @return The string up to the ]]>. * @throws JSONException If the ]]> is not found. */ @@ -40,7 +42,7 @@ public class XMLTokener extends JSONTokener { char c; int i; StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (end()) { throw syntaxError("Unclosed CDATA"); @@ -55,7 +57,7 @@ public class XMLTokener extends JSONTokener { } /** - * Get the next XML outer token, trimming whitespace. + * Get the next XML outer token, trimming whitespace. * There are two kinds of tokens: the '>' character which begins a markup tag, and the content text between markup tags. * * @return A string, or a '>' Character, or null if there is no more source text. @@ -74,7 +76,7 @@ public class XMLTokener extends JSONTokener { return XML.LT; } sb = new StringBuilder(); - for (;;) { + for (; ; ) { if (c == '<' || c == 0) { back(); return sb.toString().trim(); @@ -90,14 +92,14 @@ public class XMLTokener extends JSONTokener { /** * Return the next entity. These entities are translated to Characters: & ' > < ". - * + * * @param ampersand An ampersand character. * @return A Character or an entity String if the entity is not recognized. * @throws JSONException If missing ';' in XML entity. */ public Object nextEntity(char ampersand) throws JSONException { StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { char c = next(); if (Character.isLetterOrDigit(c) || c == '#') { sb.append(Character.toLowerCase(c)); @@ -114,7 +116,7 @@ public class XMLTokener extends JSONTokener { /** * Returns the next XML meta token. This is used for skipping over <!...> and <?...?> structures. - * + * * @return Syntax characters (< > / = ! ?) are returned as Character, and strings and names are returned as Boolean. We don't care what the values actually are. * @throws JSONException 字符串中属性未关闭或XML结构错误抛出此异常。If a string is not properly closed or if the XML is badly structured. */ @@ -142,7 +144,7 @@ public class XMLTokener extends JSONTokener { case '"': case '\'': q = c; - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -152,7 +154,7 @@ public class XMLTokener extends JSONTokener { } } default: - for (;;) { + for (; ; ) { c = next(); if (Character.isWhitespace(c)) { return Boolean.TRUE; @@ -177,7 +179,7 @@ public class XMLTokener extends JSONTokener { /** * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these characters: / > = ! ? or it may be a string wrapped in single quotes or double * quotes, or it may be a name. - * + * * @return a String or a Character. * @throws JSONException If the XML is not well formed. */ @@ -210,7 +212,7 @@ public class XMLTokener extends JSONTokener { case '\'': q = c; sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -229,7 +231,7 @@ public class XMLTokener extends JSONTokener { // Name sb = new StringBuilder(); - for (;;) { + for (; ; ) { sb.append(c); c = next(); if (Character.isWhitespace(c)) { @@ -258,7 +260,7 @@ public class XMLTokener extends JSONTokener { /** * Skip characters until past the requested string. If it is not found, we are left at the end of the source with a result of false. - * + * * @param to A string to skip past. * @return 是否成功skip * @throws JSONException JSON异常 @@ -286,7 +288,7 @@ public class XMLTokener extends JSONTokener { /* We will loop, possibly for all of the remaining characters. */ - for (;;) { + for (; ; ) { j = offset; b = true; diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 08fc8908e..b6cdf73ea 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -189,9 +189,8 @@ public class JSONObjectTest { public void toBeanTest3() { String jsonStr = "{'data':{'userName':'ak','password': null}}"; UserWithMap user = JSONUtil.toBean(JSONUtil.parseObj(jsonStr), UserWithMap.class); - String password = user.getData().get("password"); - Assert.assertTrue(user.getData().containsKey("password")); - Assert.assertNull(password); + // Bean默认忽略null + Assert.assertFalse(user.getData().containsKey("password")); } @Test diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java index aa5965b6c..4ed56d226 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java @@ -1,15 +1,10 @@ package cn.hutool.json.test.bean; +import lombok.Data; + import java.util.Map; +@Data public class UserWithMap { private Map data; - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } }