add new JSONParser

This commit is contained in:
Looly 2024-09-12 20:09:15 +08:00
parent fe676fd933
commit 7debdcef60
8 changed files with 430 additions and 155 deletions

View File

@ -29,7 +29,13 @@ import java.lang.reflect.Type;
import java.util.function.Predicate;
/**
* JSON接口
* JSON树模型接口表示树中的一个节点实现包括
* <ul>
* <li>{@link JSONObject}表示键值对形式的节点</li>
* <li>{@link JSONArray}表示列表形式的节点</li>
* <li>{@link JSONPrimitive}表示数字Boolean字符串形式的节点</li>
* <li>{@code null}表示空节点</li>
* </ul>
*
* @author Looly
*/

View File

@ -30,7 +30,7 @@ import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.json.*;
import org.dromara.hutool.json.reader.JSONParser;
import org.dromara.hutool.json.reader.OldJSONParser;
import org.dromara.hutool.json.reader.JSONTokener;
import org.dromara.hutool.json.serializer.*;
@ -202,7 +202,7 @@ public class JSONConverter implements Converter, Serializable {
}
// RFC8259JSON字符串值number, boolean, or null
final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), config);
final OldJSONParser jsonParser = OldJSONParser.of(new JSONTokener(jsonStr), config);
final Object value = jsonParser.nextValue();
if (jsonParser.getTokener().nextClean() != JSONTokener.EOF) {
// 对于用户提供的未转义字符串导致解析未结束报错

View File

@ -24,7 +24,7 @@ import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.json.JSONArray;
import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.reader.JSONParser;
import org.dromara.hutool.json.reader.OldJSONParser;
import org.dromara.hutool.json.reader.JSONTokener;
import org.dromara.hutool.json.serializer.JSONSerializer;
import org.dromara.hutool.json.serializer.SerializerManager;
@ -98,8 +98,8 @@ public class JSONArrayMapper {
if (source instanceof JSONTokener) {
mapFromTokener((JSONTokener) source, JSONConfig.of(), jsonArray);
}if (source instanceof JSONParser) {
((JSONParser)source).parseTo(jsonArray, this.predicate);
}if (source instanceof OldJSONParser) {
((OldJSONParser)source).parseTo(jsonArray, this.predicate);
} else if (source instanceof CharSequence) {
// JSON字符串
mapFromStr((CharSequence) source, jsonArray);
@ -164,6 +164,6 @@ public class JSONArrayMapper {
* @param jsonArray {@link JSONArray}
*/
private void mapFromTokener(final JSONTokener x, final JSONConfig config, final JSONArray jsonArray) {
JSONParser.of(x, config).parseTo(jsonArray, this.predicate);
OldJSONParser.of(x, config).parseTo(jsonArray, this.predicate);
}
}

View File

@ -25,7 +25,7 @@ import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.json.*;
import org.dromara.hutool.json.reader.JSONParser;
import org.dromara.hutool.json.reader.OldJSONParser;
import org.dromara.hutool.json.reader.JSONTokener;
import org.dromara.hutool.json.serializer.JSONSerializer;
import org.dromara.hutool.json.serializer.SerializerManager;
@ -111,9 +111,9 @@ public class JSONObjectMapper {
if (source instanceof JSONTokener) {
// JSONTokener
mapFromTokener((JSONTokener) source, jsonObject.config(), jsonObject);
}if (source instanceof JSONParser) {
}if (source instanceof OldJSONParser) {
// JSONParser
((JSONParser) source).parseTo(jsonObject, this.predicate);
((OldJSONParser) source).parseTo(jsonObject, this.predicate);
} else if (source instanceof Map) {
// Map
for (final Map.Entry<?, ?> e : ((Map<?, ?>) source).entrySet()) {
@ -191,7 +191,7 @@ public class JSONObjectMapper {
* @param jsonObject {@link JSONObject}
*/
private void mapFromTokener(final JSONTokener x, final JSONConfig config, final JSONObject jsonObject) {
JSONParser.of(x, config).parseTo(jsonObject, this.predicate);
OldJSONParser.of(x, config).parseTo(jsonObject, this.predicate);
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024 Hutool Team and hutool.cn
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,6 @@
package org.dromara.hutool.json.reader;
import org.dromara.hutool.core.lang.mutable.Mutable;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.json.*;
@ -31,7 +30,7 @@ import java.util.function.Predicate;
* </ul>
*
* @author looly
* @since 5.8.0
* @since 6.0.0
*/
public class JSONParser {
@ -51,6 +50,7 @@ public class JSONParser {
*/
private final JSONConfig config;
private final JSONTokener tokener;
private Predicate<MutableEntry<Object, Object>> predicate;
/**
* 构造
@ -73,72 +73,94 @@ public class JSONParser {
}
/**
* 是否结束
* 获取下一个值可以是
* <pre>
* JSONObject
* JSONArray
* JSONPrimitive
* null
* </pre>
*
* @return 是否结束
* @return JSON值
*/
public boolean end() {
return this.tokener.end();
public JSON nextValue() {
return nextValue(tokener.nextClean());
}
// region parseTo
/**
* 获取下一个值可以是
* <pre>
* JSONObject
* JSONArray
* JSONPrimitive
* null
* </pre>
*
* @return JSON值
*/
private JSON nextValue(final char c) {
switch (c) {
case CharUtil.DELIM_START:
final JSONObject jsonObject = new JSONObject(tokener, config);
nextJSONObject(jsonObject);
return jsonObject;
case CharUtil.BRACKET_START:
final JSONArray jsonArray = new JSONArray(tokener, config);
nextJSONArray(jsonArray);
return jsonArray;
default:
return nextJSONPrimitive(c);
}
}
/**
* 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}
* 解析为JSONObject
*
* @param jsonObject {@link JSONObject}
* @param predicate 键值对过滤编辑器可以通过实现此接口完成解析前对键值对的过滤和修改操作{@code null}表示不过滤{@link Predicate#test(Object)}{@code true}保留
* @param jsonObject JSON对象
*/
public void parseTo(final JSONObject jsonObject, final Predicate<MutableEntry<String, Object>> predicate) {
private void nextJSONObject(final JSONObject jsonObject) {
final JSONTokener tokener = this.tokener;
if (tokener.nextClean() != '{') {
throw tokener.syntaxError("A JSONObject text must begin with '{'");
}
char prev;
char c;
String key;
while (true) {
prev = tokener.getPrevious();
for (; ; ) {
c = tokener.nextClean();
switch (c) {
case 0:
throw tokener.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
case '{':
case '[':
if (prev == '{') {
throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
}
default:
tokener.back();
key = tokener.nextString();
if (c == CharUtil.DELIM_END) {// 对象结束
return;
} else {
key = tokener.nextKey(c);
}
// The key is followed by ':'.
tokener.nextColon();
c = tokener.nextClean();
if (c != ':') {
throw tokener.syntaxError("Expected a ':' after a key");
// 过滤并设置键值对
Object value = nextValue();
// 添加前置过滤通过MutablePair实现过滤修改键值对等
if (null != predicate) {
final MutableEntry<Object, Object> pair = new MutableEntry<>(key, value);
if (predicate.test(pair)) {
// 使用修改后的键值对
key = (String) pair.getKey();
value = pair.getValue();
jsonObject.set(key, value);
}
}else {
jsonObject.set(key, value);
}
jsonObject.set(key, nextValue(), predicate);
// Pairs are separated by ','.
// Pairs are separated by ',' or ';'
switch (tokener.nextClean()) {
case ';':
case CharUtil.COMMA:
if (tokener.nextClean() == '}') {
if (tokener.nextClean() == CharUtil.DELIM_END) {
// issue#2380
// 尾后逗号Trailing CommasJSON中虽然不支持但是ECMAScript 2017支持此处做兼容
return;
}
tokener.back();
break;
case '}':
case CharUtil.DELIM_END:
return;
default:
throw tokener.syntaxError("Expected a ',' or '}'");
@ -147,111 +169,58 @@ public class JSONParser {
}
/**
* 解析JSON字符串到{@link JSONArray}
* 解析为JSONArray
*
* @param jsonArray {@link JSONArray}
* @param predicate 键值对过滤编辑器可以通过实现此接口完成解析前对值的过滤和修改操作{@code null} 表示不过滤{@link Predicate#test(Object)}{@code true}保留
* @param jsonArray JSON数组
*/
public void parseTo(final JSONArray jsonArray, final Predicate<Mutable<Object>> predicate) {
final JSONTokener x = this.tokener;
if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['");
}
if (x.nextClean() != ']') {
x.back();
for (; ; ) {
if (x.nextClean() == CharUtil.COMMA) {
x.back();
jsonArray.add(null, predicate);
} else {
x.back();
jsonArray.add(nextValue(), predicate);
}
switch (x.nextClean()) {
case CharUtil.COMMA:
if (x.nextClean() == ']') {
return;
}
x.back();
break;
case ']':
return;
default:
throw x.syntaxError("Expected a ',' or ']'");
}
}
}
}
// endregion
/**
* 获得下一个值值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
*
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
* @throws JSONException 语法错误
*/
public Object nextValue() throws JSONException {
return nextValue((token, tokener, config) -> {
switch (token) {
case CharUtil.DELIM_START:
try {
return new JSONObject(this, config);
} catch (final StackOverflowError e) {
throw new JSONException("JSONObject depth too large to process.", e);
}
case CharUtil.BRACKET_START:
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 objectBuilder JSON对象构建器
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
* @throws JSONException 语法错误
*/
public Object nextValue(final ObjectBuilder objectBuilder) throws JSONException {
private void nextJSONArray(final JSONArray jsonArray) {
final JSONTokener tokener = this.tokener;
final char c = tokener.nextClean();
char c;
for (; ; ) {
c = tokener.nextClean();
switch (c) {
case CharUtil.BRACKET_END:
return;
case CharUtil.COMMA:
jsonArray.add(nextValue());
default:
Object value = CharUtil.COMMA == c ? nextValue() : nextValue(c);
if (null != predicate) {
// 使用过滤器
final MutableEntry<Object, Object> pair = MutableEntry.of(jsonArray.size(), value);
if (predicate.test(pair)) {
// 使用修改后的键值对
value = pair.getValue();
jsonArray.add(value);
}
}else {
jsonArray.add(value);
}
}
}
}
/**
* 解析为JSONPrimitive或{@code null}解析值包括
* <pre>
* boolean
* number
* string
* </pre>
*
* @param c 值类型
* @return JSONPrimitive或{@code null}
*/
private JSONPrimitive nextJSONPrimitive(final char c) {
switch (c) {
case CharUtil.DOUBLE_QUOTES:
case CharUtil.SINGLE_QUOTE:
return tokener.nextString(c);
case CharUtil.DELIM_START:
case CharUtil.BRACKET_START:
tokener.back();
return objectBuilder.build(c, tokener, this.config);
// 引号包围表示字符串值
return new JSONPrimitive(tokener.nextWrapString(c));
default:
final Object value = InternalJSONUtil.parseValueFromString(tokener.nextUnwrapString(c));
// 非引号包围可能为boolean数字null等
return null == value ? null : new JSONPrimitive(value);
}
/*
* 处理无引号包装的字符串 true, false, null, number.
* 同样兼容非标准的字符串如key无引号包装
* 此方法会不断读取并积累字符直到遇到token符
*/
return InternalJSONUtil.parseValueFromString(tokener.nextUnwrapString(c));
}
/**
* 对象构建抽象通过实现此接口{@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

@ -244,12 +244,55 @@ public class JSONTokener extends ReaderWrapper {
char c;
while (true) {
c = this.next();
if (c == 0 || c > ' ') {
if (c == EOF || c > ' ') {
return c;
}
}
}
/**
* 读取下一个JSON中的key支持不带引号的key
*
* @param c 当前字符
* @return 键字符串
* @throws JSONException 非引号包裹的字符串
*/
public String nextKey(final char c) throws JSONException {
final char prev = this.previous;
switch (c) {
case JSONTokener.EOF:
// 未关闭对象
throw syntaxError("A JSONObject text must end with '}'");
case CharUtil.DELIM_START:
case CharUtil.BRACKET_END:
if (prev == CharUtil.DELIM_START) {
// 不允许嵌套对象{{}}{[]}
throw syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
}
case CharUtil.DOUBLE_QUOTES:
case CharUtil.SINGLE_QUOTE:
// 带有包装的key
return nextWrapString(c);
default:
// 兼容不严格的JSON如key不被引号包围的情况
return nextUnwrapString(c);
}
}
/**
* 获取下一个冒号非冒号则抛出异常
*
* @throws JSONException 非冒号字符
* @return 冒号字符
*/
public char nextColon() throws JSONException {
final char c = nextClean();
if (c != CharUtil.COLON) {
throw syntaxError("Expected a ':' after a key");
}
return c;
}
/**
* 读取一个字符串包括
* <ul>
@ -265,7 +308,7 @@ public class JSONTokener extends ReaderWrapper {
switch (c) {
case CharUtil.DOUBLE_QUOTES:
case CharUtil.SINGLE_QUOTE:
return nextString(c);
return nextWrapString(c);
}
// 兼容不严格的JSON如key不被双引号包围的情况
@ -311,7 +354,7 @@ public class JSONTokener extends ReaderWrapper {
* @return 截止到引号前的字符串
* @throws JSONException 出现无结束的字符串时抛出此异常
*/
public String nextString(final char quote) throws JSONException {
public String nextWrapString(final char quote) throws JSONException {
char c;
final StringBuilder sb = new StringBuilder();
while (true) {

View File

@ -0,0 +1,257 @@
/*
* Copyright (c) 2013-2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.json.reader;
import org.dromara.hutool.core.lang.mutable.Mutable;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.json.*;
import java.util.function.Predicate;
/**
* JSON字符串解析器实现
* <ul>
* <li>JSON字符串 --&gt; {@link JSONTokener} --&gt; {@link JSONObject}</li>
* <li>JSON字符串 --&gt; {@link JSONTokener} --&gt; {@link JSONArray}</li>
* </ul>
*
* @author looly
* @since 5.8.0
*/
public class OldJSONParser {
/**
* 创建JSONParser
*
* @param tokener {@link JSONTokener}
* @param config JSON配置
* @return JSONParser
*/
public static OldJSONParser of(final JSONTokener tokener, final JSONConfig config) {
return new OldJSONParser(tokener, config);
}
/**
* JSON配置
*/
private final JSONConfig config;
private final JSONTokener tokener;
/**
* 构造
*
* @param tokener {@link JSONTokener}
* @param config JSON配置
*/
public OldJSONParser(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
/**
* 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}
*
* @param jsonObject {@link JSONObject}
* @param predicate 键值对过滤编辑器可以通过实现此接口完成解析前对键值对的过滤和修改操作{@code null}表示不过滤{@link Predicate#test(Object)}{@code true}保留
*/
public void parseTo(final JSONObject jsonObject, final Predicate<MutableEntry<String, Object>> predicate) {
final JSONTokener tokener = this.tokener;
if (tokener.nextClean() != '{') {
throw tokener.syntaxError("A JSONObject text must begin with '{'");
}
char prev;
char c;
String key;
while (true) {
prev = tokener.getPrevious();
c = tokener.nextClean();
switch (c) {
case 0:
throw tokener.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
case '{':
case '[':
if (prev == '{') {
throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
}
default:
tokener.back();
key = tokener.nextString();
}
// The key is followed by ':'.
c = tokener.nextClean();
if (c != ':') {
throw tokener.syntaxError("Expected a ':' after a key");
}
jsonObject.set(key, nextValue(), predicate);
// Pairs are separated by ','.
switch (tokener.nextClean()) {
case ';':
case CharUtil.COMMA:
if (tokener.nextClean() == '}') {
// issue#2380
// 尾后逗号Trailing CommasJSON中虽然不支持但是ECMAScript 2017支持此处做兼容
return;
}
tokener.back();
break;
case '}':
return;
default:
throw tokener.syntaxError("Expected a ',' or '}'");
}
}
}
/**
* 解析JSON字符串到{@link JSONArray}
*
* @param jsonArray {@link JSONArray}
* @param predicate 键值对过滤编辑器可以通过实现此接口完成解析前对值的过滤和修改操作{@code null} 表示不过滤{@link Predicate#test(Object)}{@code true}保留
*/
public void parseTo(final JSONArray jsonArray, final Predicate<Mutable<Object>> predicate) {
final JSONTokener x = this.tokener;
if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['");
}
if (x.nextClean() != ']') {
x.back();
for (; ; ) {
if (x.nextClean() == CharUtil.COMMA) {
x.back();
jsonArray.add(null, predicate);
} else {
x.back();
jsonArray.add(nextValue(), predicate);
}
switch (x.nextClean()) {
case CharUtil.COMMA:
if (x.nextClean() == ']') {
return;
}
x.back();
break;
case ']':
return;
default:
throw x.syntaxError("Expected a ',' or ']'");
}
}
}
}
// endregion
/**
* 获得下一个值值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
*
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
* @throws JSONException 语法错误
*/
public Object nextValue() throws JSONException {
return nextValue((token, tokener, config) -> {
switch (token) {
case CharUtil.DELIM_START:
try {
return new JSONObject(this, config);
} catch (final StackOverflowError e) {
throw new JSONException("JSONObject depth too large to process.", e);
}
case CharUtil.BRACKET_START:
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 objectBuilder JSON对象构建器
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
* @throws JSONException 语法错误
*/
public Object nextValue(final ObjectBuilder objectBuilder) throws JSONException {
final JSONTokener tokener = this.tokener;
final char c = tokener.nextClean();
switch (c) {
case CharUtil.DOUBLE_QUOTES:
case CharUtil.SINGLE_QUOTE:
return tokener.nextWrapString(c);
case CharUtil.DELIM_START:
case CharUtil.BRACKET_START:
tokener.back();
return objectBuilder.build(c, tokener, this.config);
}
/*
* 处理无引号包装的字符串 true, false, null, number.
* 同样兼容非标准的字符串如key无引号包装
* 此方法会不断读取并积累字符直到遇到token符
*/
return InternalJSONUtil.parseValueFromString(tokener.nextUnwrapString(c));
}
/**
* 对象构建抽象通过实现此接口{@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

@ -15,7 +15,7 @@
*/
/**
* JSON读取和解析
* JSON读取和解析主要解析字符串流等JSON字符串为{@link org.dromara.hutool.json.JSON}
*
* @author Looly
*/