Looly 5 years ago
parent
commit
d8baf83474

+ 2 - 1
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)
 
 -------------------------------------------------------------------------------------------------------------
 

+ 11 - 4
hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java

@@ -2385,8 +2385,12 @@ public class StrUtil {
 	}
 
 	/**
-	 * 将对象转为字符串<br>
-	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+	 * 将对象转为字符串
+	 *
+	 * <pre>
+	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 2、对象数组会调用Arrays.toString方法
+	 * <pre/>
 	 *
 	 * @param obj         对象
 	 * @param charsetName 字符集
@@ -2397,8 +2401,11 @@ public class StrUtil {
 	}
 
 	/**
-	 * 将对象转为字符串<br>
-	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+	 * 将对象转为字符串
+	 * <pre>
+	 * 	 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
+	 * 	 2、对象数组会调用Arrays.toString方法
+	 * <pre/>
 	 *
 	 * @param obj     对象
 	 * @param charset 字符集

+ 1 - 1
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;

+ 9 - 2
hutool-json/src/main/java/cn/hutool/json/JSONArray.java

@@ -189,6 +189,11 @@ public class JSONArray implements JSON, JSONGetter<Integer>, List<Object>, Rando
 	}
 	// -------------------------------------------------------------------------------------------------------------------- Constructor start
 
+	@Override
+	public JSONConfig getConfig() {
+		return this.config;
+	}
+
 	/**
 	 * 设置转为字符串时的日期格式,默认为时间戳(null值)
 	 * 
@@ -295,7 +300,7 @@ public class JSONArray implements JSON, JSONGetter<Integer>, List<Object>, 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<Integer>, List<Object>, 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<Integer>, List<Object>, Rando
 	 */
 	private void init(CharSequence source) {
 		if (null != source) {
-			init(new JSONTokener(StrUtil.trim(source)));
+			init(new JSONTokener(StrUtil.trim(source), this.config));
 		}
 	}
 

+ 43 - 35
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 <K> Key类型
+ * @author Looly
  */
-public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K>{
-	
+public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K> {
+
+	/**
+	 * 获取JSON配置
+	 *
+	 * @return {@link JSONConfig}
+	 * @since 5.3.0
+	 */
+	JSONConfig getConfig();
+
 	/**
 	 * key对应值是否为<code>null</code>或无此key
 	 *
@@ -20,10 +28,10 @@ public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K>{
 	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<K> extends OptNullBasicTypeFromObjectGetter<K>{
 	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<K> extends OptNullBasicTypeFromObjectGetter<K>{
 	default String getStrEscaped(K key, String defaultValue) {
 		return JSONUtil.escape(getStr(key, defaultValue));
 	}
-	
+
 	/**
 	 * 获得JSONArray对象<br>
 	 * 如果值为其它类型对象,尝试转换为{@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对象<br>
 	 * 如果值为其它类型对象,尝试转换为{@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对象<br>
 	 * 先获取JSONObject对象,然后转为Bean对象
-	 * 
-	 * @param <T> Bean类型
-	 * @param key KEY
+	 *
+	 * @param <T>      Bean类型
+	 * @param key      KEY
 	 * @param beanType Bean类型
 	 * @return Bean对象,如果值为null或者非JSONObject类型,返回null
 	 * @since 3.1.1
@@ -96,36 +104,36 @@ public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K>{
 		final JSONObject obj = getJSONObject(key);
 		return (null == obj) ? null : obj.toBean(beanType);
 	}
-	
+
 	/**
 	 * 获取指定类型的对象<br>
 	 * 转换失败或抛出异常
-	 * 
-	 * @param <T> 获取的对象类型
-	 * @param key 键
+	 *
+	 * @param <T>  获取的对象类型
+	 * @param key  
 	 * @param type 获取对象类型
 	 * @return 对象
 	 * @throws ConvertException 转换异常
 	 * @since 3.0.8
 	 */
-	default <T> T get(K key, Class<T> type) throws ConvertException{
+	default <T> T get(K key, Class<T> type) throws ConvertException {
 		return get(key, type, false);
 	}
-	
+
 	/**
 	 * 获取指定类型的对象
-	 * 
-	 * @param <T> 获取的对象类型
-	 * @param key 键
-	 * @param type 获取对象类型
+	 *
+	 * @param <T>         获取的对象类型
+	 * @param key         
+	 * @param type        获取对象类型
 	 * @param ignoreError 是否跳过转换失败的对象或值
 	 * @return 对象
 	 * @throws ConvertException 转换异常
 	 * @since 3.0.8
 	 */
-	default <T> T get(K key, Class<T> type, boolean ignoreError) throws ConvertException{
+	default <T> T get(K key, Class<T> 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);

+ 10 - 15
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<String>, Map<String, Object>
 	 * 构造
 	 * 
 	 * @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<String>, Map<String, Object>
 
 	// -------------------------------------------------------------------------------------------------------------------- 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<String>, Map<String, Object>
 		rawHashMap.clear();
 	}
 
-	@SuppressWarnings("NullableProblems")
 	@Override
 	public Set<String> keySet() {
 		return this.rawHashMap.keySet();
 	}
 
-	@SuppressWarnings("NullableProblems")
 	@Override
 	public Collection<Object> values() {
 		return rawHashMap.values();
 	}
 
-	@SuppressWarnings("NullableProblems")
 	@Override
 	public Set<Entry<String, Object>> entrySet() {
 		return rawHashMap.entrySet();
@@ -661,7 +656,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
 	 * 
 	 * @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<String>, Map<String, Object>
 	 * @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<String>, Map<String, Object>
 		if (x.nextClean() != '{') {
 			throw x.syntaxError("A JSONObject text must begin with '{'");
 		}
-		for (;;) {
+		while (true) {
 			c = x.nextClean();
 			switch (c) {
 			case 0:

+ 19 - 9
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 '['");
 		}

+ 1 - 1
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);
 		}

+ 23 - 21
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<String, Character> entity;
 
 	static {
-		entity = new java.util.HashMap<String, Character>(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 <code>]]&gt;</code>.
 	 * @throws JSONException If the <code>]]&gt;</code> 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 '&gt;' character which begins a markup tag, and the content text between markup tags.
 	 *
 	 * @return A string, or a '&gt;' 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: <code>&amp;  '  &gt;  &lt;  &quot;</code>.
-	 * 
+	 *
 	 * @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 &lt;!...&gt; and &lt;?...?&gt; structures.
-	 * 
+	 *
 	 * @return Syntax characters (<code>&lt; &gt; / = ! ?</code>) 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: <code>/ &gt; = ! ?</code> 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;
 

+ 2 - 3
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

+ 3 - 8
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<String, String> data;
-	
-	public Map<String, String> getData() {
-		return data;
-	}
-	
-	public void setData(Map<String, String> data) {
-		this.data = data;
-	}
 }