diff --git a/core/src/main/java/com/alibaba/fastjson2/JSON.java b/core/src/main/java/com/alibaba/fastjson2/JSON.java index 4f6cf407b..ea75a7190 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSON.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSON.java @@ -246,6 +246,32 @@ public interface JSON { } } + /** + * Parses the json stream as a {@link JSONArray} or {@link JSONObject}. + * Returns {@code null} if received {@link String} is {@code null} or empty. + * + * @param in the specified stream to be parsed + * @param context the specified custom context + * @return either {@link JSONArray} or {@link JSONObject} or null + * @throws JSONException If a parsing error occurs + * @throws NullPointerException If received context is null + * @since 2.0.47 + */ + static Object parse(InputStream in, JSONReader.Context context) { + if (in == null) { + return null; + } + + ObjectReader objectReader = context.getObjectReader(Object.class); + try (JSONReaderUTF8 reader = new JSONReaderUTF8(context, in)) { + Object object = objectReader.readObject(reader, null, null, 0); + if (reader.ch != EOI && (context.features & IgnoreCheckClose.mask) == 0) { + throw new JSONException(reader.info("input not end")); + } + return object; + } + } + /** * Parses the json string as a {@link JSONObject}. Returns {@code null} * if received {@link String} is {@code null} or empty or its content is null. @@ -570,6 +596,40 @@ public interface JSON { } } + /** + * Parses the json stream as a {@link JSONObject}. Returns {@code null} if + * received {@link InputStream} is {@code null} or closed or its content is null. + * + * @param input the specified stream to be parsed + * @param charset the specified charset of the stream + * @param context the specified custom context + * @return {@link JSONObject} or {@code null} + * @throws JSONException If a parsing error occurs + * + * @since 2.0.47 + */ + static JSONObject parseObject(InputStream input, Charset charset, JSONReader.Context context) { + if (input == null) { + return null; + } + + try (JSONReader reader = JSONReader.of(input, charset, context)) { + if (reader.isEnd()) { + return null; + } + + JSONObject object = new JSONObject(); + reader.read(object, 0); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(object); + } + if (reader.ch != EOI && (context.features & IgnoreCheckClose.mask) == 0) { + throw new JSONException(reader.info("input not end")); + } + return object; + } + } + /** * Parses the json stream of the url as a {@link JSONObject}. * Returns {@code null} if received {@link URL} is {@code null}. @@ -1861,6 +1921,76 @@ public interface JSON { } } + /** + * Parses the json stream as a {@link T}. Returns {@code null} + * if received {@link InputStream} is {@code null} or its content is null. + * + * @param input the specified stream to be parsed + * @param type the specified actual type of {@link T} + * @param context the specified custom context + * @return {@link T} or {@code null} + * @throws JSONException If a parsing error occurs + */ + @SuppressWarnings("unchecked") + static T parseObject(InputStream input, Charset charset, Type type, JSONReader.Context context) { + if (input == null) { + return null; + } + + boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0; + ObjectReader objectReader = context.provider.getObjectReader(type, fieldBased); + + try (JSONReader reader = JSONReader.of(input, charset, context)) { + if (reader.isEnd()) { + return null; + } + + T object = objectReader.readObject(reader, type, null, 0); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(object); + } + if (reader.ch != EOI && (context.features & IgnoreCheckClose.mask) == 0) { + throw new JSONException(reader.info("input not end")); + } + return object; + } + } + + /** + * Parses the json stream as a {@link T}. Returns {@code null} + * if received {@link InputStream} is {@code null} or its content is null. + * + * @param input the specified stream to be parsed + * @param type the specified actual type of {@link T} + * @param context the specified custom context + * @return {@link T} or {@code null} + * @throws JSONException If a parsing error occurs + */ + @SuppressWarnings("unchecked") + static T parseObject(InputStream input, Charset charset, Class type, JSONReader.Context context) { + if (input == null) { + return null; + } + + boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0; + ObjectReader objectReader = context.provider.getObjectReader(type, fieldBased); + + try (JSONReader reader = JSONReader.of(input, charset, context)) { + if (reader.isEnd()) { + return null; + } + + T object = objectReader.readObject(reader, type, null, 0); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(object); + } + if (reader.ch != EOI && (context.features & IgnoreCheckClose.mask) == 0) { + throw new JSONException(reader.info("input not end")); + } + return object; + } + } + /** * Parses the json stream of the url as {@link T}. * Returns {@code null} if received {@link URL} is {@code null}. @@ -2498,6 +2628,37 @@ public interface JSON { } } + /** + * Parses the json stream as a {@link JSONArray}. Returns {@code null} + * if received {@link InputStream} is {@code null} or its content is null. + * + * @param in the specified stream to be parsed + * @param charset the specified charset of the stream + * @param context the specified custom context + * @return {@link JSONArray} or {@code null} + * @throws JSONException If an I/O error or parsing error occurs + */ + static JSONArray parseArray(InputStream in, Charset charset, JSONReader.Context context) { + if (in == null) { + return null; + } + + try (JSONReader reader = JSONReader.of(in, charset, context)) { + if (reader.nextIfNull()) { + return null; + } + JSONArray array = new JSONArray(); + reader.read(array); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(array); + } + if (reader.ch != EOI && (context.features & IgnoreCheckClose.mask) == 0) { + throw new JSONException(reader.info("input not end")); + } + return array; + } + } + /** * Parses the json string as a list of {@link T}. Returns * {@code null} if received {@link String} is {@code null} or empty. diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONB.java b/core/src/main/java/com/alibaba/fastjson2/JSONB.java index f301dc39b..c981f5459 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONB.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONB.java @@ -302,7 +302,6 @@ public interface JSONB { static Object parse(byte[] jsonbBytes, JSONReader.Feature... features) { ObjectReaderProvider provider = defaultObjectReaderProvider; JSONReader.Context context = new JSONReader.Context(provider, features); - boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0; try (JSONReaderJSONB reader = new JSONReaderJSONB( context, @@ -310,9 +309,17 @@ public interface JSONB { 0, jsonbBytes.length) ) { - ObjectReader objectReader = provider.getObjectReader(Object.class, fieldBased); + Object object = reader.readAny(); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(object); + } + return object; + } + } - Object object = objectReader.readJSONBObject(reader, null, null, 0); + static Object parse(InputStream in, JSONReader.Context context) { + try (JSONReaderJSONB reader = new JSONReaderJSONB(context, in)) { + Object object = reader.readAny(); if (reader.resolveTasks != null) { reader.handleResolveTasks(object); } @@ -323,7 +330,6 @@ public interface JSONB { static Object parse(byte[] jsonbBytes, SymbolTable symbolTable, JSONReader.Feature... features) { ObjectReaderProvider provider = defaultObjectReaderProvider; JSONReader.Context context = new JSONReader.Context(provider, symbolTable, features); - boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0; try (JSONReaderJSONB reader = new JSONReaderJSONB( context, @@ -331,9 +337,7 @@ public interface JSONB { 0, jsonbBytes.length) ) { - ObjectReader objectReader = provider.getObjectReader(Object.class, fieldBased); - - Object object = objectReader.readJSONBObject(reader, null, null, 0); + Object object = reader.readAny(); if (reader.resolveTasks != null) { reader.handleResolveTasks(object); } @@ -371,6 +375,16 @@ public interface JSONB { } } + static JSONObject parseObject(InputStream in, JSONReader.Context context) { + try (JSONReaderJSONB reader = new JSONReaderJSONB(context, in)) { + JSONObject object = (JSONObject) reader.readObject(); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(object); + } + return object; + } + } + static JSONArray parseArray(byte[] jsonbBytes) { try (JSONReaderJSONB reader = new JSONReaderJSONB( new JSONReader.Context(JSONFactory.defaultObjectReaderProvider), @@ -386,6 +400,16 @@ public interface JSONB { } } + static JSONArray parseArray(InputStream in, JSONReader.Context context) { + try (JSONReaderJSONB reader = new JSONReaderJSONB(context, in)) { + JSONArray array = (JSONArray) reader.readArray(); + if (reader.resolveTasks != null) { + reader.handleResolveTasks(array); + } + return array; + } + } + static List parseArray(byte[] jsonbBytes, Type type) { if (jsonbBytes == null || jsonbBytes.length == 0) { return null; diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReader.java b/core/src/main/java/com/alibaba/fastjson2/JSONReader.java index c6ab0ab88..1741994fd 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONReader.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONReader.java @@ -2904,6 +2904,10 @@ public abstract class JSONReader return new JSONReaderUTF16(context, is); } + if (charset == StandardCharsets.US_ASCII) { + return new JSONReaderASCII(context, is); + } + throw new JSONException("not support charset " + charset); } diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderASCII.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderASCII.java index c20b63e63..59b93d343 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderASCII.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderASCII.java @@ -3,6 +3,7 @@ package com.alibaba.fastjson2; import com.alibaba.fastjson2.util.*; import sun.misc.Unsafe; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import static com.alibaba.fastjson2.JSONFactory.*; @@ -18,6 +19,12 @@ final class JSONReaderASCII nameAscii = true; } + JSONReaderASCII(Context ctx, InputStream is) { + super(ctx, is); + nameAscii = true; + str = null; + } + @Override public void next() { int offset = this.offset; diff --git a/core/src/test/java/com/alibaba/fastjson2/features/TrimStringTest.java b/core/src/test/java/com/alibaba/fastjson2/features/TrimStringTest.java index eadfa6df5..62b0e764b 100644 --- a/core/src/test/java/com/alibaba/fastjson2/features/TrimStringTest.java +++ b/core/src/test/java/com/alibaba/fastjson2/features/TrimStringTest.java @@ -1,11 +1,10 @@ package com.alibaba.fastjson2.features; -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONB; -import com.alibaba.fastjson2.JSONObject; -import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.*; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -25,13 +24,92 @@ public class TrimStringTest { .getString("value")); assertEquals("a b", - JSON.parseObject(utf8Bytes, 0, utf8Bytes.length, StandardCharsets.US_ASCII, JSONReader.Feature.TrimString) - .getString("value")); + JSON.parseObject( + utf8Bytes, 0, utf8Bytes.length, StandardCharsets.US_ASCII, JSONReader.Feature.TrimString + ).getString("value") + ); + assertEquals("a b", + JSON.parseObject( + new ByteArrayInputStream(utf8Bytes), + StandardCharsets.US_ASCII, + new JSONReader.Context(JSONReader.Feature.TrimString) + ).getString("value") + ); + assertEquals("a b", + ((JSONObject) JSON.parse( + new ByteArrayInputStream(utf8Bytes), + new JSONReader.Context(JSONReader.Feature.TrimString) + )).getString("value") + ); + assertEquals("a b", + JSON.parseObject( + new ByteArrayInputStream(utf8Bytes), + StandardCharsets.US_ASCII, + Bean.class, + new JSONReader.Context(JSONReader.Feature.TrimString) + ).value + ); + assertEquals("a b", + ((Bean) JSON.parseObject( + new ByteArrayInputStream(utf8Bytes), + StandardCharsets.US_ASCII, + (Type) Bean.class, + new JSONReader.Context(JSONReader.Feature.TrimString) + )).value + ); + + assertEquals("a b", + JSON.parseArray( + new ByteArrayInputStream("[\" a b \"]".getBytes(StandardCharsets.UTF_8)), + StandardCharsets.US_ASCII, + new JSONReader.Context(JSONReader.Feature.TrimString) + ).get(0) + ); } @Test public void testJSONB() { byte[] jsonbBytes = JSONObject.of("value", " a b ").toJSONBBytes(); - assertEquals("a b", JSONB.parseObject(jsonbBytes, JSONReader.Feature.TrimString).getString("value")); + String result = "a b"; + assertEquals( + result, + JSONB.parseObject( + new ByteArrayInputStream(jsonbBytes), + new JSONReader.Context(JSONReader.Feature.TrimString) + ).getString("value") + ); + assertEquals( + result, + ((JSONObject) JSONB.parse( + jsonbBytes, + new JSONReader.Context(JSONReader.Feature.TrimString) + )).getString("value") + ); + assertEquals( + result, + JSONB.parseObject( + jsonbBytes, + JSONReader.Feature.TrimString + ).getString("value") + ); + assertEquals( + result, + ((JSONObject) JSONB.parse( + new ByteArrayInputStream(jsonbBytes), + new JSONReader.Context(JSONReader.Feature.TrimString) + )).getString("value") + ); + + assertEquals( + result, + JSONB.parseArray( + new ByteArrayInputStream(JSONArray.of(" a b ").toJSONBBytes()), + new JSONReader.Context(JSONReader.Feature.TrimString) + ).getString(0) + ); + } + + public static class Bean { + public String value; } }