mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-24 18:04:54 +08:00
fix code
This commit is contained in:
parent
5a4fbd4386
commit
75f9a66998
@ -30,14 +30,20 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public class JSONParser {
|
||||
|
||||
/**
|
||||
* JSON配置
|
||||
*/
|
||||
private final JSONConfig config;
|
||||
|
||||
/**
|
||||
* 创建JSONParser
|
||||
*
|
||||
* @param tokener {@link JSONTokener}
|
||||
* @param config JSON配置
|
||||
* @return JSONParser
|
||||
*/
|
||||
public static JSONParser of(final JSONTokener tokener) {
|
||||
return new JSONParser(tokener);
|
||||
public static JSONParser of(final JSONTokener tokener, final JSONConfig config) {
|
||||
return new JSONParser(tokener, config);
|
||||
}
|
||||
|
||||
private final JSONTokener tokener;
|
||||
@ -46,9 +52,29 @@ public class JSONParser {
|
||||
* 构造
|
||||
*
|
||||
* @param tokener {@link JSONTokener}
|
||||
* @param config JSON配置
|
||||
*/
|
||||
public JSONParser(final JSONTokener tokener) {
|
||||
public JSONParser(final JSONTokener tokener, final JSONConfig config) {
|
||||
this.tokener = tokener;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link JSONTokener}
|
||||
*
|
||||
* @return {@link JSONTokener}
|
||||
*/
|
||||
public JSONTokener getTokener() {
|
||||
return this.tokener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否结束
|
||||
*
|
||||
* @return 是否结束
|
||||
*/
|
||||
public boolean end() {
|
||||
return this.tokener.end();
|
||||
}
|
||||
|
||||
// region parseTo
|
||||
@ -79,12 +105,12 @@ public class JSONParser {
|
||||
return;
|
||||
case '{':
|
||||
case '[':
|
||||
if(prev=='{') {
|
||||
if (prev == '{') {
|
||||
throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
|
||||
}
|
||||
default:
|
||||
tokener.back();
|
||||
key = tokener.nextValue(true).toString();
|
||||
key = nextValue(true).toString();
|
||||
}
|
||||
|
||||
// The key is followed by ':'.
|
||||
@ -94,7 +120,7 @@ public class JSONParser {
|
||||
throw tokener.syntaxError("Expected a ':' after a key");
|
||||
}
|
||||
|
||||
jsonObject.set(key, tokener.nextValue(false), predicate);
|
||||
jsonObject.set(key, nextValue(false), predicate);
|
||||
|
||||
// Pairs are separated by ','.
|
||||
|
||||
@ -136,7 +162,7 @@ public class JSONParser {
|
||||
jsonArray.addRaw(null, predicate);
|
||||
} else {
|
||||
x.back();
|
||||
jsonArray.addRaw(x.nextValue(false), predicate);
|
||||
jsonArray.addRaw(nextValue(false), predicate);
|
||||
}
|
||||
switch (x.nextClean()) {
|
||||
case CharUtil.COMMA:
|
||||
@ -154,4 +180,91 @@ public class JSONParser {
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
*
|
||||
* @param getOnlyStringValue 是否只获取String值
|
||||
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
* @throws JSONException 语法错误
|
||||
*/
|
||||
public Object nextValue(final boolean getOnlyStringValue) throws JSONException {
|
||||
return nextValue(getOnlyStringValue, (token, tokener, config) -> {
|
||||
switch (token) {
|
||||
case '{':
|
||||
try {
|
||||
return new JSONObject(this, config);
|
||||
} catch (final StackOverflowError e) {
|
||||
throw new JSONException("JSONObject depth too large to process.", e);
|
||||
}
|
||||
case '[':
|
||||
try {
|
||||
return new JSONArray(this, config);
|
||||
} catch (final StackOverflowError e) {
|
||||
throw new JSONException("JSONObject depth too large to process.", e);
|
||||
}
|
||||
}
|
||||
throw new JSONException("Unsupported object build for token {}", token);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
*
|
||||
* @param getOnlyStringValue 是否只获取String值
|
||||
* @param objectBuilder JSON对象构建器
|
||||
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
* @throws JSONException 语法错误
|
||||
*/
|
||||
public Object nextValue(final boolean getOnlyStringValue, final ObjectBuilder objectBuilder) throws JSONException {
|
||||
final JSONTokener tokener = this.tokener;
|
||||
char c = tokener.nextClean();
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\'':
|
||||
return tokener.nextString(c);
|
||||
case '{':
|
||||
case '[':
|
||||
if (getOnlyStringValue) {
|
||||
throw tokener.syntaxError("String value must not begin with '{'");
|
||||
}
|
||||
tokener.back();
|
||||
return objectBuilder.build(c, tokener, this.config);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle unquoted text. This could be the values true, false, or null, or it can be a number.
|
||||
* An implementation (such as this one) is allowed to also accept non-standard forms. Accumulate
|
||||
* characters until we reach the end of the text or a formatting character.
|
||||
*/
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
|
||||
sb.append(c);
|
||||
c = tokener.next();
|
||||
}
|
||||
tokener.back();
|
||||
|
||||
final String valueString = sb.toString().trim();
|
||||
if (valueString.isEmpty()) {
|
||||
throw tokener.syntaxError("Missing value");
|
||||
}
|
||||
return getOnlyStringValue ? valueString : InternalJSONUtil.parseValueFromString(valueString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象构建抽象,通过实现此接口,从{@link JSONTokener}解析值并构建指定对象
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ObjectBuilder {
|
||||
/**
|
||||
* 构建
|
||||
*
|
||||
* @param token 符号表示,用于区分对象类型
|
||||
* @param tokener {@link JSONTokener}
|
||||
* @param config {@link JSONConfig}
|
||||
* @return 构建的对象
|
||||
*/
|
||||
Object build(char token, JSONTokener tokener, JSONConfig config);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ import java.io.StringReader;
|
||||
*/
|
||||
public class JSONTokener extends ReaderWrapper {
|
||||
|
||||
/**
|
||||
* 定义结束(End of stream)为:0
|
||||
*/
|
||||
public static final int EOF = 0;
|
||||
|
||||
private long character;
|
||||
/**
|
||||
* 是否结尾 End of stream
|
||||
@ -51,41 +56,33 @@ public class JSONTokener extends ReaderWrapper {
|
||||
*/
|
||||
private boolean usePrevious;
|
||||
|
||||
/**
|
||||
* JSON配置
|
||||
*/
|
||||
private final JSONConfig config;
|
||||
|
||||
// ------------------------------------------------------------------------------------ Constructor start
|
||||
|
||||
/**
|
||||
* 从InputStream中构建,使用UTF-8编码
|
||||
*
|
||||
* @param inputStream InputStream
|
||||
* @param config JSON配置
|
||||
* @throws JSONException JSON异常,包装IO异常
|
||||
*/
|
||||
public JSONTokener(final InputStream inputStream, final JSONConfig config) throws JSONException {
|
||||
this(IoUtil.toUtf8Reader(inputStream), config);
|
||||
public JSONTokener(final InputStream inputStream) throws JSONException {
|
||||
this(IoUtil.toUtf8Reader(inputStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串中构建
|
||||
*
|
||||
* @param s JSON字符串
|
||||
* @param config JSON配置
|
||||
* @param s JSON字符串
|
||||
*/
|
||||
public JSONTokener(final CharSequence s, final JSONConfig config) {
|
||||
this(new StringReader(Assert.notBlank(s).toString()), config);
|
||||
public JSONTokener(final CharSequence s) {
|
||||
this(new StringReader(Assert.notBlank(s).toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Reader中构建
|
||||
*
|
||||
* @param reader Reader
|
||||
* @param config JSON配置
|
||||
*/
|
||||
public JSONTokener(final Reader reader, final JSONConfig config) {
|
||||
public JSONTokener(final Reader reader) {
|
||||
super(IoUtil.toMarkSupport(Assert.notNull(reader)));
|
||||
this.eof = false;
|
||||
this.usePrevious = false;
|
||||
@ -93,7 +90,6 @@ public class JSONTokener extends ReaderWrapper {
|
||||
this.index = 0;
|
||||
this.character = 1;
|
||||
this.line = 1;
|
||||
this.config = config;
|
||||
}
|
||||
// ------------------------------------------------------------------------------------ Constructor end
|
||||
|
||||
@ -152,9 +148,9 @@ public class JSONTokener extends ReaderWrapper {
|
||||
throw new JSONException(exception);
|
||||
}
|
||||
|
||||
if (c <= 0) { // End of stream
|
||||
if (c <= EOF) { // End of stream
|
||||
this.eof = true;
|
||||
c = 0;
|
||||
c = EOF;
|
||||
}
|
||||
}
|
||||
this.index += 1;
|
||||
@ -183,9 +179,10 @@ public class JSONTokener extends ReaderWrapper {
|
||||
/**
|
||||
* 获取16进制unicode转义符对应的字符值,如:
|
||||
* <pre>{@code '4f60' -> '你'}</pre>
|
||||
*
|
||||
* @return 字符
|
||||
*/
|
||||
public char nextUnicode(){
|
||||
public char nextUnicode() {
|
||||
return (char) NumberUtil.parseInt(next(4), 16);
|
||||
}
|
||||
|
||||
@ -332,61 +329,6 @@ public class JSONTokener extends ReaderWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
*
|
||||
* @param getOnlyStringValue 是否只获取String值
|
||||
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||
* @throws JSONException 语法错误
|
||||
*/
|
||||
public Object nextValue(final boolean getOnlyStringValue) throws JSONException {
|
||||
char c = this.nextClean();
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\'':
|
||||
return this.nextString(c);
|
||||
case '{':
|
||||
if (getOnlyStringValue) {
|
||||
throw this.syntaxError("String value must not begin with '{'");
|
||||
}
|
||||
this.back();
|
||||
try {
|
||||
return new JSONObject(this, this.config);
|
||||
} catch (final StackOverflowError e) {
|
||||
throw new JSONException("JSONObject depth too large to process.", e);
|
||||
}
|
||||
case '[':
|
||||
if (getOnlyStringValue) {
|
||||
throw this.syntaxError("String value must not begin with '['");
|
||||
}
|
||||
this.back();
|
||||
try {
|
||||
return new JSONArray(this, this.config);
|
||||
} catch (final StackOverflowError e) {
|
||||
throw new JSONException("JSONArray depth too large to process.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle unquoted text. This could be the values true, false, or null, or it can be a number.
|
||||
* An implementation (such as this one) is allowed to also accept non-standard forms. Accumulate
|
||||
* characters until we reach the end of the text or a formatting character.
|
||||
*/
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
|
||||
sb.append(c);
|
||||
c = this.next();
|
||||
}
|
||||
this.back();
|
||||
|
||||
final String valueString = sb.toString().trim();
|
||||
if (valueString.isEmpty()) {
|
||||
throw this.syntaxError("Missing value");
|
||||
}
|
||||
return getOnlyStringValue ? valueString : InternalJSONUtil.parseValueFromString(valueString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip characters until the next character is the requested character. If the requested character is not found, no characters are skipped. 在遇到指定字符前,跳过其它字符。如果字符未找到,则不跳过任何字符。
|
||||
*
|
||||
|
@ -103,14 +103,14 @@ public class JSONConverter implements Converter, Serializable {
|
||||
|
||||
// 对象转JSON
|
||||
final Class<?> targetClass = TypeUtil.getClass(targetType);
|
||||
if(null != targetClass){
|
||||
if (null != targetClass) {
|
||||
if (JSON.class.isAssignableFrom(targetClass)) {
|
||||
return toJSON(value);
|
||||
}
|
||||
// 自定义日期格式
|
||||
if(Date.class.isAssignableFrom(targetClass) || TemporalAccessor.class.isAssignableFrom(targetClass)){
|
||||
if (Date.class.isAssignableFrom(targetClass) || TemporalAccessor.class.isAssignableFrom(targetClass)) {
|
||||
final Object date = toDateWithFormat(targetClass, value);
|
||||
if(null != date){
|
||||
if (null != date) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
@ -120,7 +120,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现Object对象转换为{@link JSON},支持的对象:
|
||||
* 实现Object对象转换为JSON对象,根据RFC8259规范,支持的对象:
|
||||
* <ul>
|
||||
* <li>String: 转换为相应的对象,"和'包围的字符串返回原字符串,""返回{@code null}</li>
|
||||
* <li>Array、Iterable、Iterator:转换为JSONArray</li>
|
||||
@ -133,15 +133,14 @@ public class JSONConverter implements Converter, Serializable {
|
||||
* @return 转换后的对象
|
||||
* @throws JSONException 转换异常
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public Object toJSON(Object obj) throws JSONException {
|
||||
if(null == obj){
|
||||
if (null == obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(obj instanceof Optional){
|
||||
if (obj instanceof Optional) {
|
||||
obj = ((Optional<?>) obj).orElse(null);
|
||||
} else if(obj instanceof Opt){
|
||||
} else if (obj instanceof Opt) {
|
||||
obj = ((Opt<?>) obj).get();
|
||||
}
|
||||
|
||||
@ -149,26 +148,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
if (obj instanceof JSON || obj instanceof Number || obj instanceof Boolean) {
|
||||
return obj;
|
||||
} else if (obj instanceof CharSequence) {
|
||||
final String jsonStr = StrUtil.trim((CharSequence) obj);
|
||||
if(jsonStr.isEmpty()){
|
||||
// 空按照null值处理
|
||||
return null;
|
||||
}
|
||||
final char firstC = jsonStr.charAt(0);
|
||||
switch (firstC){
|
||||
case '[':
|
||||
return new JSONArray(jsonStr, config);
|
||||
case '{':
|
||||
return new JSONObject(jsonStr, config);
|
||||
default:
|
||||
// RFC8259,JSON字符串值、number, boolean, or null
|
||||
final Object value = new JSONTokener(jsonStr, config).nextValue(false);
|
||||
if(ObjUtil.equals(value, jsonStr)){
|
||||
// 非可解析的字符串,原样返回
|
||||
return InternalJSONUtil.quote((CharSequence) value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return toJSON((CharSequence) obj);
|
||||
} else if (obj instanceof MapWrapper) {
|
||||
// MapWrapper实现了Iterable会被当作JSONArray,此处做修正
|
||||
json = new JSONObject(obj, config);
|
||||
@ -181,6 +161,46 @@ public class JSONConverter implements Converter, Serializable {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现{@link CharSequence}转换为JSON对象,根据RFC8259规范<br>
|
||||
* 转换为相应的对象,"和'包围的字符串返回原字符串,""返回{@code null}
|
||||
*
|
||||
* @param str 被转换的字符串
|
||||
* @return 转换后的对象
|
||||
* @throws JSONException 转换异常
|
||||
*/
|
||||
public Object toJSON(final CharSequence str) throws JSONException {
|
||||
if (null == str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String jsonStr = StrUtil.trim(str);
|
||||
if (jsonStr.isEmpty()) {
|
||||
// https://www.rfc-editor.org/rfc/rfc8259#section-7
|
||||
// 未被包装的空串理解为null
|
||||
return null;
|
||||
}
|
||||
final char firstC = jsonStr.charAt(0);
|
||||
// RFC8259,JSON字符串值、number, boolean, or null
|
||||
final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), config);
|
||||
final Object value = jsonParser.nextValue(false);
|
||||
if(jsonParser.getTokener().nextClean() != JSONTokener.EOF){
|
||||
// 对于用户提供的未转义字符串导致解析未结束,报错
|
||||
throw new JSONException("JSON format error: {}", jsonStr);
|
||||
}
|
||||
switch (firstC) {
|
||||
case '"':
|
||||
case '\'':
|
||||
return InternalJSONUtil.quote((CharSequence) value);
|
||||
default:
|
||||
if(ObjUtil.equals(jsonStr, value)){
|
||||
// 对于直接的字符串,如abc,按照字符串处理
|
||||
return InternalJSONUtil.quote((CharSequence) value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
@ -221,13 +241,13 @@ public class JSONConverter implements Converter, Serializable {
|
||||
// 尝试转Bean
|
||||
if (BeanUtil.isWritableBean(rawType)) {
|
||||
// issue#I5WDP0 对于Kotlin对象,由于参数可能非空限制,导致无法创建一个默认的对象再赋值
|
||||
if(KClassUtil.isKotlinClass(rawType) && json instanceof JSONGetter){
|
||||
return KClassUtil.newInstance(rawType, new JSONGetterValueProvider<>((JSONGetter<String>)json));
|
||||
if (KClassUtil.isKotlinClass(rawType) && json instanceof JSONGetter) {
|
||||
return KClassUtil.newInstance(rawType, new JSONGetterValueProvider<>((JSONGetter<String>) json));
|
||||
}
|
||||
|
||||
return BeanCopier.of(json,
|
||||
ConstructorUtil.newInstanceIfPossible(rawType), targetType,
|
||||
InternalJSONUtil.toCopyOptions(json.config())).copy();
|
||||
ConstructorUtil.newInstanceIfPossible(rawType), targetType,
|
||||
InternalJSONUtil.toCopyOptions(json.config())).copy();
|
||||
}
|
||||
|
||||
// 跳过异常时返回null
|
||||
@ -237,7 +257,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
|
||||
// 无法转换
|
||||
throw new JSONException("Can not convert from '{}': {} to '{}'",
|
||||
json.getClass().getName(), json, targetType.getTypeName());
|
||||
json.getClass().getName(), json, targetType.getTypeName());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,7 +284,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
}
|
||||
|
||||
// 日期、java.sql中的日期以及自定义日期统一处理
|
||||
if(Date.class.isAssignableFrom(rowType)){
|
||||
if (Date.class.isAssignableFrom(rowType)) {
|
||||
return (T) DateConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
@ -279,7 +299,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
}
|
||||
|
||||
// issue#I6SZYB Entry类(含有泛型参数,不可以默认强转)
|
||||
if(Map.Entry.class.isAssignableFrom(rowType)){
|
||||
if (Map.Entry.class.isAssignableFrom(rowType)) {
|
||||
return (T) EntryConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
@ -294,12 +314,12 @@ public class JSONConverter implements Converter, Serializable {
|
||||
}
|
||||
|
||||
// Record
|
||||
if(RecordUtil.isRecord(rowType)){
|
||||
if (RecordUtil.isRecord(rowType)) {
|
||||
return (T) RecordConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
// 空值转空Bean
|
||||
if(ObjUtil.isEmpty(value)){
|
||||
if (ObjUtil.isEmpty(value)) {
|
||||
// issue#3649 空值转空对象,则直接实例化
|
||||
return ConstructorUtil.newInstanceIfPossible(rowType);
|
||||
}
|
||||
@ -308,7 +328,7 @@ public class JSONConverter implements Converter, Serializable {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object toDateWithFormat(final Class<?> targetClass, final Object value){
|
||||
private Object toDateWithFormat(final Class<?> targetClass, final Object value) {
|
||||
// 日期转换,支持自定义日期格式
|
||||
final String format = config.getDateFormat();
|
||||
if (StrUtil.isNotBlank(format)) {
|
||||
|
@ -17,10 +17,7 @@ import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.mutable.Mutable;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.json.JSONArray;
|
||||
import org.dromara.hutool.json.JSONException;
|
||||
import org.dromara.hutool.json.JSONParser;
|
||||
import org.dromara.hutool.json.JSONTokener;
|
||||
import org.dromara.hutool.json.*;
|
||||
import org.dromara.hutool.json.serialize.GlobalSerializeMapping;
|
||||
import org.dromara.hutool.json.serialize.JSONSerializer;
|
||||
|
||||
@ -91,18 +88,20 @@ public class JSONArrayMapper {
|
||||
}
|
||||
|
||||
if (source instanceof JSONTokener) {
|
||||
mapFromTokener((JSONTokener) source, jsonArray);
|
||||
}else if (source instanceof CharSequence) {
|
||||
mapFromTokener((JSONTokener) source, JSONConfig.of(), jsonArray);
|
||||
}if (source instanceof JSONParser) {
|
||||
((JSONParser)source).parseTo(jsonArray, this.predicate);
|
||||
} else if (source instanceof CharSequence) {
|
||||
// JSON字符串
|
||||
mapFromStr((CharSequence) source, jsonArray);
|
||||
} else if (source instanceof Reader) {
|
||||
mapFromTokener(new JSONTokener((Reader) source, jsonArray.config()), jsonArray);
|
||||
mapFromTokener(new JSONTokener((Reader) source), jsonArray.config(), jsonArray);
|
||||
} else if (source instanceof InputStream) {
|
||||
mapFromTokener(new JSONTokener((InputStream) source, jsonArray.config()), jsonArray);
|
||||
mapFromTokener(new JSONTokener((InputStream) source), jsonArray.config(), jsonArray);
|
||||
} else if (source instanceof byte[]) {
|
||||
final byte[] bytesSource = (byte[]) source;
|
||||
if ('[' == bytesSource[0] && ']' == bytesSource[bytesSource.length - 1]) {
|
||||
mapFromTokener(new JSONTokener(IoUtil.toStream(bytesSource), jsonArray.config()), jsonArray);
|
||||
mapFromTokener(new JSONTokener(IoUtil.toStream(bytesSource)), jsonArray.config(), jsonArray);
|
||||
} else {
|
||||
// https://github.com/dromara/hutool/issues/2369
|
||||
// 非标准的二进制流,则按照普通数组对待
|
||||
@ -119,8 +118,8 @@ public class JSONArrayMapper {
|
||||
} else if (source instanceof Iterable<?>) {// Iterable
|
||||
iter = ((Iterable<?>) source).iterator();
|
||||
} else {
|
||||
if(!jsonArray.config().isIgnoreError()){
|
||||
throw new JSONException("JSONArray initial value should be a string or collection or array.");
|
||||
if (!jsonArray.config().isIgnoreError()) {
|
||||
throw new JSONException("Unsupported [{}] to JSONArray", source.getClass());
|
||||
}
|
||||
// 如果用户选择跳过异常,则跳过此值转换
|
||||
return;
|
||||
@ -145,7 +144,7 @@ public class JSONArrayMapper {
|
||||
*/
|
||||
private void mapFromStr(final CharSequence source, final JSONArray jsonArray) {
|
||||
if (null != source) {
|
||||
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonArray.config()), jsonArray);
|
||||
mapFromTokener(new JSONTokener(StrUtil.trim(source)), jsonArray.config(), jsonArray);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +154,7 @@ public class JSONArrayMapper {
|
||||
* @param x {@link JSONTokener}
|
||||
* @param jsonArray {@link JSONArray}
|
||||
*/
|
||||
private void mapFromTokener(final JSONTokener x, final JSONArray jsonArray) {
|
||||
JSONParser.of(x).parseTo(jsonArray, this.predicate);
|
||||
private void mapFromTokener(final JSONTokener x, final JSONConfig config, final JSONArray jsonArray) {
|
||||
JSONParser.of(x, config).parseTo(jsonArray, this.predicate);
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,10 @@ public class JSONObjectMapper {
|
||||
|
||||
if (source instanceof JSONTokener) {
|
||||
// JSONTokener
|
||||
mapFromTokener((JSONTokener) source, jsonObject);
|
||||
mapFromTokener((JSONTokener) source, jsonObject.config(), jsonObject);
|
||||
}if (source instanceof JSONParser) {
|
||||
// JSONParser
|
||||
((JSONParser) source).parseTo(jsonObject, this.predicate);
|
||||
} else if (source instanceof Map) {
|
||||
// Map
|
||||
for (final Map.Entry<?, ?> e : ((Map<?, ?>) source).entrySet()) {
|
||||
@ -116,11 +119,11 @@ public class JSONObjectMapper {
|
||||
// 可能为JSON字符串
|
||||
mapFromStr((CharSequence) source, jsonObject);
|
||||
} else if (source instanceof Reader) {
|
||||
mapFromTokener(new JSONTokener((Reader) source, jsonObject.config()), jsonObject);
|
||||
mapFromTokener(new JSONTokener((Reader) source), jsonObject.config(), jsonObject);
|
||||
} else if (source instanceof InputStream) {
|
||||
mapFromTokener(new JSONTokener((InputStream) source, jsonObject.config()), jsonObject);
|
||||
mapFromTokener(new JSONTokener((InputStream) source), jsonObject.config(), jsonObject);
|
||||
} else if (source instanceof byte[]) {
|
||||
mapFromTokener(new JSONTokener(IoUtil.toStream((byte[]) source), jsonObject.config()), jsonObject);
|
||||
mapFromTokener(new JSONTokener(IoUtil.toStream((byte[]) source)), jsonObject.config(), jsonObject);
|
||||
} else if (source instanceof ResourceBundle) {
|
||||
// ResourceBundle
|
||||
mapFromResourceBundle((ResourceBundle) source, jsonObject);
|
||||
@ -170,17 +173,18 @@ public class JSONObjectMapper {
|
||||
JSONXMLParser.of(ParseConfig.of(), this.predicate).parseJSONObject(jsonStr, jsonObject);
|
||||
return;
|
||||
}
|
||||
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject);
|
||||
mapFromTokener(new JSONTokener(StrUtil.trim(source)), jsonObject.config(), jsonObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从{@link JSONTokener}转换
|
||||
*
|
||||
* @param x JSONTokener
|
||||
* @param config JSON配置
|
||||
* @param jsonObject {@link JSONObject}
|
||||
*/
|
||||
private void mapFromTokener(final JSONTokener x, final JSONObject jsonObject) {
|
||||
JSONParser.of(x).parseTo(jsonObject, this.predicate);
|
||||
private void mapFromTokener(final JSONTokener x, final JSONConfig config, final JSONObject jsonObject) {
|
||||
JSONParser.of(x, config).parseTo(jsonObject, this.predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +64,7 @@ public class JSONXMLParser {
|
||||
* @throws JSONException 解析异常
|
||||
*/
|
||||
public void parseJSONObject(final String xmlStr, final JSONObject jo) throws JSONException {
|
||||
final XMLTokener x = new XMLTokener(xmlStr, jo.config());
|
||||
final XMLTokener x = new XMLTokener(xmlStr);
|
||||
while (x.more() && x.skipPast("<")) {
|
||||
parse(x, jo, null, 0);
|
||||
}
|
||||
|
@ -44,10 +44,9 @@ public class XMLTokener extends JSONTokener {
|
||||
* Construct an XMLTokener from a string.
|
||||
*
|
||||
* @param s A source string.
|
||||
* @param config JSON配置
|
||||
*/
|
||||
public XMLTokener(final CharSequence s, final JSONConfig config) {
|
||||
super(s, config);
|
||||
public XMLTokener(final CharSequence s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
package org.dromara.hutool.json;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.date.DateUtil;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
@ -20,48 +21,68 @@ import org.dromara.hutool.json.serialize.JSONStringer;
|
||||
import org.dromara.hutool.json.test.bean.Price;
|
||||
import org.dromara.hutool.json.test.bean.UserA;
|
||||
import org.dromara.hutool.json.test.bean.UserC;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class JSONUtilTest {
|
||||
|
||||
@Test
|
||||
public void parseInvalid() {
|
||||
Assertions.assertThrows(JSONException.class, ()->{
|
||||
assertThrows(JSONException.class, () -> {
|
||||
JSONUtil.parse("'abc");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInvalid3() {
|
||||
Assertions.assertThrows(JSONException.class, ()->{
|
||||
assertThrows(JSONException.class, () -> {
|
||||
JSONUtil.parse("\"abc");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseEmptyValue() {
|
||||
// https://www.rfc-editor.org/rfc/rfc8259#section-7
|
||||
// 未被包装的空串理解为null
|
||||
Object parse = JSONUtil.parse("");
|
||||
assertNull(parse);
|
||||
|
||||
parse = JSONUtil.parse("\"\"");
|
||||
assertEquals("\"\"", parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseValueTest() {
|
||||
Object parse = JSONUtil.parse(123);
|
||||
Assertions.assertEquals(123, parse);
|
||||
assertEquals(123, parse);
|
||||
|
||||
parse = JSONUtil.parse("\"abc\"");
|
||||
Assertions.assertEquals("abc", parse);
|
||||
assertEquals("\"abc\"", parse);
|
||||
|
||||
parse = JSONUtil.parse("\"\\\"bc\"");
|
||||
assertEquals("\"\\\"bc\"", parse);
|
||||
|
||||
parse = JSONUtil.parse("true");
|
||||
Assertions.assertEquals(true, parse);
|
||||
assertEquals(true, parse);
|
||||
|
||||
parse = JSONUtil.parse("False");
|
||||
Assertions.assertEquals(false, parse);
|
||||
assertEquals(false, parse);
|
||||
|
||||
parse = JSONUtil.parse("null");
|
||||
Assertions.assertNull(parse);
|
||||
assertNull(parse);
|
||||
}
|
||||
|
||||
parse = JSONUtil.parse("");
|
||||
Assertions.assertNull(parse);
|
||||
@Test
|
||||
void parseInvalidTest() {
|
||||
assertThrows(JSONException.class, () -> {
|
||||
// 包装符需要转义,此处未转义报错
|
||||
JSONUtil.parse("\"a\"bc\"");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +90,7 @@ public class JSONUtilTest {
|
||||
*/
|
||||
@Test
|
||||
public void parseTest() {
|
||||
Assertions.assertThrows(JSONException.class, ()->{
|
||||
assertThrows(JSONException.class, () -> {
|
||||
JSONUtil.parseArray("[{\"a\":\"a\\x]");
|
||||
});
|
||||
}
|
||||
@ -79,7 +100,7 @@ public class JSONUtilTest {
|
||||
*/
|
||||
@Test
|
||||
public void parseNumberToJSONArrayTest() {
|
||||
Assertions.assertThrows(JSONException.class, ()->{
|
||||
assertThrows(JSONException.class, () -> {
|
||||
final JSONArray json = JSONUtil.parseArray(123L);
|
||||
Assertions.assertNotNull(json);
|
||||
});
|
||||
@ -91,7 +112,7 @@ public class JSONUtilTest {
|
||||
@Test
|
||||
public void parseNumberToJSONArrayTest2() {
|
||||
final JSONArray json = JSONUtil.parseArray(123L,
|
||||
JSONConfig.of().setIgnoreError(true));
|
||||
JSONConfig.of().setIgnoreError(true));
|
||||
Assertions.assertNotNull(json);
|
||||
}
|
||||
|
||||
@ -100,7 +121,7 @@ public class JSONUtilTest {
|
||||
*/
|
||||
@Test
|
||||
public void parseNumberToJSONObjectTest() {
|
||||
Assertions.assertThrows(JSONException.class, ()->{
|
||||
assertThrows(JSONException.class, () -> {
|
||||
final JSONObject json = JSONUtil.parseObj(123L);
|
||||
Assertions.assertNotNull(json);
|
||||
});
|
||||
@ -112,7 +133,7 @@ public class JSONUtilTest {
|
||||
@Test
|
||||
public void parseNumberToJSONObjectTest2() {
|
||||
final JSONObject json = JSONUtil.parseObj(123L, JSONConfig.of().setIgnoreError(true));
|
||||
Assertions.assertEquals(new JSONObject(), json);
|
||||
assertEquals(new JSONObject(), json);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -149,8 +170,8 @@ public class JSONUtilTest {
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(data);
|
||||
|
||||
Assertions.assertTrue(jsonObject.containsKey("model"));
|
||||
Assertions.assertEquals(1, jsonObject.getJSONObject("model").getInt("type").intValue());
|
||||
Assertions.assertEquals("17610836523", jsonObject.getJSONObject("model").getStr("mobile"));
|
||||
assertEquals(1, jsonObject.getJSONObject("model").getInt("type").intValue());
|
||||
assertEquals("17610836523", jsonObject.getJSONObject("model").getStr("mobile"));
|
||||
// Assertions.assertEquals("{\"model\":{\"type\":1,\"mobile\":\"17610836523\"}}", jsonObject.toString());
|
||||
}
|
||||
|
||||
@ -166,11 +187,11 @@ public class JSONUtilTest {
|
||||
map.put("user", object.toString());
|
||||
|
||||
final JSONObject json = JSONUtil.parseObj(map);
|
||||
Assertions.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"</\"}", json.get("user"));
|
||||
Assertions.assertEquals("{\"user\":\"{\\\"name\\\":\\\"123123\\\",\\\"value\\\":\\\"\\\\\\\\\\\",\\\"value2\\\":\\\"</\\\"}\"}", json.toString());
|
||||
assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"</\"}", json.get("user"));
|
||||
assertEquals("{\"user\":\"{\\\"name\\\":\\\"123123\\\",\\\"value\\\":\\\"\\\\\\\\\\\",\\\"value2\\\":\\\"</\\\"}\"}", json.toString());
|
||||
|
||||
final JSONObject json2 = JSONUtil.parseObj(json.toString());
|
||||
Assertions.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"</\"}", json2.get("user"));
|
||||
assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"</\"}", json2.get("user"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -180,12 +201,13 @@ public class JSONUtilTest {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("attributes", "a");
|
||||
put("b", "b");
|
||||
put("c", "c");
|
||||
}};
|
||||
put("attributes", "a");
|
||||
put("b", "b");
|
||||
put("c", "c");
|
||||
}
|
||||
};
|
||||
|
||||
Assertions.assertEquals("{\"attributes\":\"a\",\"b\":\"b\",\"c\":\"c\"}", JSONUtil.toJsonStr(sortedMap));
|
||||
assertEquals("{\"attributes\":\"a\",\"b\":\"b\",\"c\":\"c\"}", JSONUtil.toJsonStr(sortedMap));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +218,7 @@ public class JSONUtilTest {
|
||||
final String json = "{\"ADT\":[[{\"BookingCode\":[\"N\",\"N\"]}]]}";
|
||||
|
||||
final Price price = JSONUtil.toBean(json, Price.class);
|
||||
Assertions.assertEquals("N", price.getADT().get(0).get(0).getBookingCode().get(0));
|
||||
assertEquals("N", price.getADT().get(0).get(0).getBookingCode().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -207,8 +229,8 @@ public class JSONUtilTest {
|
||||
Assertions.assertNotNull(user.getProp());
|
||||
final String prop = user.getProp();
|
||||
final JSONObject propJson = JSONUtil.parseObj(prop);
|
||||
Assertions.assertEquals("男", propJson.getStr("gender"));
|
||||
Assertions.assertEquals(18, propJson.getInt("age").intValue());
|
||||
assertEquals("男", propJson.getStr("gender"));
|
||||
assertEquals(18, propJson.getInt("age").intValue());
|
||||
// Assertions.assertEquals("{\"age\":18,\"gender\":\"男\"}", user.getProp());
|
||||
}
|
||||
|
||||
@ -216,24 +238,24 @@ public class JSONUtilTest {
|
||||
public void getStrTest() {
|
||||
final String html = "{\"name\":\"Something must have been changed since you leave\"}";
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(html);
|
||||
Assertions.assertEquals("Something must have been changed since you leave", jsonObject.getStr("name"));
|
||||
assertEquals("Something must have been changed since you leave", jsonObject.getStr("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStrTest2() {
|
||||
final String html = "{\"name\":\"Something\\u00a0must have been changed since you leave\"}";
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(html);
|
||||
Assertions.assertEquals("Something\\u00a0must\\u00a0have\\u00a0been\\u00a0changed\\u00a0since\\u00a0you\\u00a0leave", jsonObject.getStrEscaped("name"));
|
||||
assertEquals("Something\\u00a0must\\u00a0have\\u00a0been\\u00a0changed\\u00a0since\\u00a0you\\u00a0leave", jsonObject.getStrEscaped("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseFromXmlTest() {
|
||||
final String s = "<sfzh>640102197312070614</sfzh><sfz>640102197312070614X</sfz><name>aa</name><gender>1</gender>";
|
||||
final JSONObject json = JSONUtil.parseFromXml(s);
|
||||
Assertions.assertEquals(640102197312070614L, json.get("sfzh"));
|
||||
Assertions.assertEquals("640102197312070614X", json.get("sfz"));
|
||||
Assertions.assertEquals("aa", json.get("name"));
|
||||
Assertions.assertEquals(1, json.get("gender"));
|
||||
assertEquals(640102197312070614L, json.get("sfzh"));
|
||||
assertEquals("640102197312070614X", json.get("sfz"));
|
||||
assertEquals("aa", json.get("name"));
|
||||
assertEquals(1, json.get("gender"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -241,81 +263,81 @@ public class JSONUtilTest {
|
||||
final String json = "{\"test\": 12.00}";
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(json);
|
||||
//noinspection BigDecimalMethodWithoutRoundingCalled
|
||||
Assertions.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
|
||||
assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customValueTest() {
|
||||
final JSONObject jsonObject = JSONUtil.ofObj()
|
||||
.set("test2", (JSONStringer) () -> NumberUtil.format("#.0", 12.00D));
|
||||
.set("test2", (JSONStringer) () -> NumberUtil.format("#.0", 12.00D));
|
||||
|
||||
Assertions.assertEquals("{\"test2\":12.0}", jsonObject.toString());
|
||||
assertEquals("{\"test2\":12.0}", jsonObject.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setStripTrailingZerosTest() {
|
||||
// 默认去除多余的0
|
||||
final JSONObject jsonObjectDefault = JSONUtil.ofObj()
|
||||
.set("test2", 12.00D);
|
||||
Assertions.assertEquals("{\"test2\":12}", jsonObjectDefault.toString());
|
||||
.set("test2", 12.00D);
|
||||
assertEquals("{\"test2\":12}", jsonObjectDefault.toString());
|
||||
|
||||
// 不去除多余的0
|
||||
final JSONObject jsonObject = JSONUtil.ofObj(JSONConfig.of().setStripTrailingZeros(false))
|
||||
.set("test2", 12.00D);
|
||||
Assertions.assertEquals("{\"test2\":12.0}", jsonObject.toString());
|
||||
.set("test2", 12.00D);
|
||||
assertEquals("{\"test2\":12.0}", jsonObject.toString());
|
||||
|
||||
// 去除多余的0
|
||||
jsonObject.config().setStripTrailingZeros(true);
|
||||
Assertions.assertEquals("{\"test2\":12}", jsonObject.toString());
|
||||
assertEquals("{\"test2\":12}", jsonObject.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseObjTest() {
|
||||
// 测试转义
|
||||
final JSONObject jsonObject = JSONUtil.parseObj("{\n" +
|
||||
" \"test\": \"\\\\地库地库\",\n" +
|
||||
"}");
|
||||
" \"test\": \"\\\\地库地库\",\n" +
|
||||
"}");
|
||||
|
||||
Assertions.assertEquals("\\地库地库", jsonObject.getObj("test"));
|
||||
assertEquals("\\地库地库", jsonObject.getObj("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sqlExceptionTest(){
|
||||
public void sqlExceptionTest() {
|
||||
//https://github.com/dromara/hutool/issues/1399
|
||||
// SQLException实现了Iterable接口,默认是遍历之,会栈溢出,修正后只返回string
|
||||
final JSONObject set = JSONUtil.ofObj().set("test", new SQLException("test"));
|
||||
Assertions.assertEquals("{\"test\":\"java.sql.SQLException: test\"}", set.toString());
|
||||
assertEquals("{\"test\":\"java.sql.SQLException: test\"}", set.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBigNumberTest(){
|
||||
public void parseBigNumberTest() {
|
||||
// 科学计数法使用BigDecimal处理,默认输出非科学计数形式
|
||||
final String str = "{\"test\":100000054128897953e4}";
|
||||
Assertions.assertEquals("{\"test\":1000000541288979530000}", JSONUtil.parseObj(str).toString());
|
||||
assertEquals("{\"test\":1000000541288979530000}", JSONUtil.parseObj(str).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toXmlTest(){
|
||||
public void toXmlTest() {
|
||||
final JSONObject obj = JSONUtil.ofObj();
|
||||
obj.set("key1", "v1")
|
||||
.set("key2", ListUtil.view("a", "b", "c"));
|
||||
.set("key2", ListUtil.view("a", "b", "c"));
|
||||
final String xmlStr = JSONUtil.toXmlStr(obj);
|
||||
Assertions.assertEquals("<key1>v1</key1><key2>a</key2><key2>b</key2><key2>c</key2>", xmlStr);
|
||||
assertEquals("<key1>v1</key1><key2>a</key2><key2>b</key2><key2>c</key2>", xmlStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toJsonStrOfStringTest(){
|
||||
public void toJsonStrOfStringTest() {
|
||||
final String a = "a";
|
||||
|
||||
final String s = JSONUtil.toJsonStr(a);
|
||||
Assertions.assertEquals("\"a\"", s);
|
||||
assertEquals("\"a\"", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toJsonStrOfNumberTest(){
|
||||
public void toJsonStrOfNumberTest() {
|
||||
final int a = 1;
|
||||
final String s = JSONUtil.toJsonStr(a);
|
||||
Assertions.assertEquals("1", s);
|
||||
assertEquals("1", s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -325,7 +347,7 @@ public class JSONUtilTest {
|
||||
public void testArrayEntity() {
|
||||
final String jsonStr = JSONUtil.toJsonStr(new ArrayEntity());
|
||||
// a为空的bytes数组,按照空的流对待
|
||||
Assertions.assertEquals("{\"b\":[0],\"c\":[],\"d\":[],\"e\":[]}", jsonStr);
|
||||
assertEquals("{\"b\":[0],\"c\":[],\"d\":[],\"e\":[]}", jsonStr);
|
||||
}
|
||||
|
||||
@Data
|
||||
@ -340,13 +362,13 @@ public class JSONUtilTest {
|
||||
@Test
|
||||
void toJsonStrOfBooleanTest() {
|
||||
final String jsonStr = JSONUtil.toJsonStr(true);
|
||||
Assertions.assertEquals("true", jsonStr);
|
||||
assertEquals("true", jsonStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue3540Test() {
|
||||
final Long userId=10101010L;
|
||||
final Long userId = 10101010L;
|
||||
final String jsonStr = JSONUtil.toJsonStr(userId);
|
||||
Assertions.assertEquals("10101010", jsonStr);
|
||||
assertEquals("10101010", jsonStr);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user