This commit is contained in:
Looly 2024-08-04 12:03:33 +08:00
parent 5a4fbd4386
commit 75f9a66998
8 changed files with 302 additions and 203 deletions

View File

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

View File

@ -29,6 +29,11 @@ import java.io.StringReader;
*/
public class JSONTokener extends ReaderWrapper {
/**
* 定义结束End of stream0
*/
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. 在遇到指定字符前跳过其它字符如果字符未找到则不跳过任何字符
*

View File

@ -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>ArrayIterableIterator转换为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:
// RFC8259JSON字符串值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);
// RFC8259JSON字符串值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)) {

View File

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

View File

@ -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);
}
/**

View File

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

View File

@ -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);
}
/**

View File

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