From cd08a43a268291d125c96d29b857c7e46b1d98d7 Mon Sep 17 00:00:00 2001 From: QIURC Date: Sun, 20 Jul 2025 22:12:25 +0800 Subject: [PATCH] Support reading multiple JSON syntax blocks in a single input (#269) --- .../minidev/json/parser/JSONParserBase.java | 19 ++ .../json/parser/JSONParserByteArray.java | 14 +- .../json/parser/JSONParserInputStream.java | 13 +- .../minidev/json/parser/JSONParserReader.java | 15 +- .../minidev/json/parser/JSONParserStream.java | 22 +- .../minidev/json/parser/JSONParserString.java | 6 + .../json/parser/MultipleJsonParser.java | 67 ++++++ .../test/parser/MultipleJsonParserTest.java | 206 ++++++++++++++++++ .../minidev/json/test/parser/Transaction.java | 77 +++++++ 9 files changed, 391 insertions(+), 48 deletions(-) create mode 100644 json-smart/src/main/java/net/minidev/json/parser/MultipleJsonParser.java create mode 100644 json-smart/src/test/java/net/minidev/json/test/parser/MultipleJsonParserTest.java create mode 100644 json-smart/src/test/java/net/minidev/json/test/parser/Transaction.java diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java index 4fbb40f..5a683f9 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java @@ -195,12 +195,31 @@ private boolean compareDoublePrecision(String convert, String origin) { return j == originArray.length; } + protected boolean hasNext() { + return c != EOI; + } + /** + * parse from the first position
* use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory */ protected T parse(JsonReaderI mapper) throws ParseException { this.pos = -1; + return parseInner(mapper); + } + + /** + * parse from the last position
+ * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a + * ContainerFactory + */ + protected T parseNext(JsonReaderI mapper) throws ParseException { + this.pos = this.pos > 0 ? this.pos : -1; + return parseInner(mapper); + } + + private T parseInner(JsonReaderI mapper) throws ParseException { T result; try { read(); diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserByteArray.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserByteArray.java index ea8035d..368abd0 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserByteArray.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserByteArray.java @@ -33,6 +33,12 @@ public JSONParserByteArray(int permissiveMode) { super(permissiveMode); } + public JSONParserByteArray(byte[] in, int permissiveMode) { + super(permissiveMode); + this.in = in; + this.len = in.length; + } + /** * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory @@ -41,14 +47,6 @@ public Object parse(byte[] in) throws ParseException { return parse(in, JSONValue.defaultReader.DEFAULT); } - // - // - // - // - // - // - // - /** * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserInputStream.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserInputStream.java index 076b302..0eac3ca 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserInputStream.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserInputStream.java @@ -33,6 +33,10 @@ public JSONParserInputStream(int permissiveMode) { super(permissiveMode); } + public JSONParserInputStream(InputStream in, int permissiveMode) { + super(new InputStreamReader(in, StandardCharsets.UTF_8), permissiveMode); + } + /** * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory @@ -54,13 +58,4 @@ public T parse(InputStream in, JsonReaderI mapper) // return super.parse(i2, mapper); } - - // - // - // - // - // - // - // - // } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserReader.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserReader.java index 2b0169b..d24e7dc 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserReader.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserReader.java @@ -35,6 +35,11 @@ public JSONParserReader(int permissiveMode) { super(permissiveMode); } + public JSONParserReader(Reader in, int permissiveMode) { + super(permissiveMode); + this.in = in; + } + /** * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory @@ -54,19 +59,10 @@ public T parse(Reader in, JsonReaderI mapper) throws ParseException { return super.parse(mapper); } - // - // - // - // - // - // - // - protected void read() throws IOException { int i = in.read(); c = (i == -1) ? (char) EOI : (char) i; pos++; - // } protected void readS() throws IOException { @@ -84,6 +80,5 @@ protected void readNoEnd() throws ParseException, IOException { int i = in.read(); if (i == -1) throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); c = (char) i; - // } } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserStream.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserStream.java index dbcc938..1ec1236 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserStream.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserStream.java @@ -28,8 +28,6 @@ * @see JSONParserReader */ abstract class JSONParserStream extends JSONParserBase { - // len - // public JSONParserStream(int permissiveMode) { super(permissiveMode); } @@ -112,26 +110,8 @@ protected void readString() throws ParseException, IOException { throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c); } sb.clear(); - // - // - // - // - // - // - // - // - // - // + /* assert (c == '\"' || c == '\'') */ readString2(); } - - // - // - // - // - // - // - // - // } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserString.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserString.java index 4d505e2..04d4ba3 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserString.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserString.java @@ -32,6 +32,12 @@ public JSONParserString(int permissiveMode) { super(permissiveMode); } + public JSONParserString(String in, int permissiveMode) { + super(permissiveMode); + this.in = in; + this.len = in.length(); + } + /** * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a * ContainerFactory diff --git a/json-smart/src/main/java/net/minidev/json/parser/MultipleJsonParser.java b/json-smart/src/main/java/net/minidev/json/parser/MultipleJsonParser.java new file mode 100644 index 0000000..76a1c68 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/parser/MultipleJsonParser.java @@ -0,0 +1,67 @@ +package net.minidev.json.parser; + +import java.io.InputStream; +import java.io.Reader; +import net.minidev.json.JSONValue; +import net.minidev.json.writer.JsonReaderI; + +/** + * json-smart will parse multiple json separated by blank or line break character. + * + *

multiple json example:
+ * {"json1": "value1"} {"json2": "value2"}
+ * or
+ * [{"json1-key1": "value1"}] [{"json2": "value2"}] + */ +public class MultipleJsonParser { + + private final JSONParserBase jsonParser; + + public MultipleJsonParser(byte[] in, int permissiveMode) { + this.jsonParser = new JSONParserByteArray(in, permissiveMode); + } + + public MultipleJsonParser(InputStream in, int permissiveMode) { + this.jsonParser = new JSONParserInputStream(in, permissiveMode); + } + + public MultipleJsonParser(Reader in, int permissiveMode) { + this.jsonParser = new JSONParserReader(in, permissiveMode); + } + + public MultipleJsonParser(String in, int permissiveMode) { + this.jsonParser = new JSONParserString(in, permissiveMode); + } + + /** + * Parse next json with defaultReader
+ * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a + * ContainerFactory + */ + public Object parseNext() throws ParseException { + return jsonParser.parseNext(JSONValue.defaultReader.DEFAULT); + } + + /** + * Parse next json with target JsonReaderI
+ * use to return Primitive Type, or String, Or JsonObject or JsonArray generated by a + * ContainerFactory + */ + public T parseNext(JsonReaderI mapper) throws ParseException { + return this.jsonParser.parseNext(mapper); + } + + /** Parse next json with target Class */ + public T parseNext(Class mapTo) throws ParseException { + return this.jsonParser.parseNext(JSONValue.defaultReader.getMapper(mapTo)); + } + + /** + * Checks if there is another JSON value available in the input. + * + * @return true if another JSON value exists, false otherwise + */ + public boolean hasNext() { + return this.jsonParser.hasNext(); + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/parser/MultipleJsonParserTest.java b/json-smart/src/test/java/net/minidev/json/test/parser/MultipleJsonParserTest.java new file mode 100644 index 0000000..7cecb8a --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/parser/MultipleJsonParserTest.java @@ -0,0 +1,206 @@ +package net.minidev.json.test.parser; + +import static net.minidev.json.parser.JSONParser.DEFAULT_PERMISSIVE_MODE; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.MultipleJsonParser; +import net.minidev.json.parser.ParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MultipleJsonParserTest { + + @Test + public void testMultipleJsonsFromSingleJsonArraySuccess() + throws ParseException, UnsupportedEncodingException { + String json = + "[{\"friends\":[{\"id\":0,\"name\":\"test1\"},{\"id\":1,\"name\":\"test2\"}]}] other data"; + MultipleJsonParser parser = new MultipleJsonParser(json, DEFAULT_PERMISSIVE_MODE); + JSONArray root = (JSONArray) parser.parseNext(); + JSONObject rootObj = (JSONObject) root.get(0); + JSONArray array = (JSONArray) rootObj.get("friends"); + for (int idx = 0; idx < array.size(); idx++) { + JSONObject cap = (JSONObject) array.get(idx); + String first = (String) cap.get("name"); + Assertions.assertEquals("test" + (idx + 1), first); + } + } + + @Test + public void testMultipleJsonsFromMultipleJsonArraySuccess() + throws ParseException, UnsupportedEncodingException { + String json = + "[{\"friends\":[{\"id\":0,\"name\":\"test1\"},{\"id\":1,\"name\":\"test2\"}]}] " + + "[{\"friends\":[{\"id\":2,\"name\":\"test3\"},{\"id\":3,\"name\":\"test4\"}]}]"; + MultipleJsonParser parser = new MultipleJsonParser(json, DEFAULT_PERMISSIVE_MODE); + + // first + JSONArray root = (JSONArray) parser.parseNext(); + JSONObject rootObj = (JSONObject) root.get(0); + JSONArray array = (JSONArray) rootObj.get("friends"); + for (int idx = 0; idx < array.size(); idx++) { + JSONObject cap = (JSONObject) array.get(idx); + String first = (String) cap.get("name"); + Assertions.assertEquals("test" + (idx + 1), first); + } + + // second + JSONArray root2 = (JSONArray) parser.parseNext(); + JSONObject rootObj2 = (JSONObject) root2.get(0); + JSONArray array2 = (JSONArray) rootObj2.get("friends"); + for (int idx = 0; idx < array2.size(); idx++) { + JSONObject cap = (JSONObject) array2.get(idx); + String first = (String) cap.get("name"); + Assertions.assertEquals("test" + (idx + 3), first); + } + } + + @Test + public void testMultipleJsonsFromByteSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + "\n{tranid:\"1213\", \"user\":{\"name\":\"345\",\"addr\":\"4234 iu\"}}"; + MultipleJsonParser parser = + new MultipleJsonParser(json.getBytes(StandardCharsets.UTF_8), DEFAULT_PERMISSIVE_MODE); + + JSONObject root1 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1212", root1.get("tranid")); + + Assertions.assertTrue(parser.hasNext()); + JSONObject root2 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1213", root2.get("tranid")); + + Assertions.assertFalse(parser.hasNext()); + } + + @Test + public void testMultipleJsonsFromInputStreamSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + "\n{tranid:\"1213\", \"user\":{\"name\":\"345\",\"addr\":\"4234 iu\"}}"; + ByteArrayInputStream stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + MultipleJsonParser parser = new MultipleJsonParser(stream, DEFAULT_PERMISSIVE_MODE); + + JSONObject root1 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1212", root1.get("tranid")); + + Assertions.assertTrue(parser.hasNext()); + JSONObject root2 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1213", root2.get("tranid")); + + Assertions.assertFalse(parser.hasNext()); + } + + @Test + public void testMultipleJsonsFromInputStreamReaderSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + "\n{tranid:\"1213\", \"user\":{\"name\":\"345\",\"addr\":\"4234 iu\"}}"; + StringReader reader = new StringReader(json); + MultipleJsonParser parser = new MultipleJsonParser(reader, DEFAULT_PERMISSIVE_MODE); + + JSONObject root1 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1212", root1.get("tranid")); + + Assertions.assertTrue(parser.hasNext()); + JSONObject root2 = (JSONObject) parser.parseNext(); + Assertions.assertEquals("1213", root2.get("tranid")); + + Assertions.assertFalse(parser.hasNext()); + } + + @Test + public void testMultipleJsonsFromStringSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + " {tranid:\"1213\", \"user\":{\"name\":\"343\",\"addr\":\"4233 iu\"}}" + + "\r{tranid:\"1214\", \"user\":{\"name\":\"344\",\"addr\":\"4234 iu\"}}" + + "\t{tranid:\"1215\", \"user\":{\"name\":\"345\",\"addr\":\"4235 iu\"}}" + + "\n{tranid:\"1216\", \"user\":{\"name\":\"346\",\"addr\":\"4236 iu\"}}"; + MultipleJsonParser parser = new MultipleJsonParser(json, DEFAULT_PERMISSIVE_MODE); + JSONObject root1 = (JSONObject) parser.parseNext(JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("1212", root1.get("tranid")); + + int count = 3; + while (parser.hasNext()) { + JSONObject root2 = (JSONObject) parser.parseNext(JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("121" + count, root2.get("tranid")); + count++; + } + Assertions.assertEquals(7, count); + } + + @Test + public void testMultipleJsonsParseClassFromStringSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + " {tranid:\"1213\", \"user\":{\"name\":\"343\",\"addr\":\"4233 iu\"}}" + + "\r{tranid:\"1214\", \"user\":{\"name\":\"344\",\"addr\":\"4234 iu\"}}" + + "\t{tranid:\"1215\", \"user\":{\"name\":\"345\",\"addr\":\"4235 iu\"}}" + + "\n{tranid:\"1216\", \"user\":{\"name\":\"346\",\"addr\":\"4236 iu\"}}"; + MultipleJsonParser parser = new MultipleJsonParser(json, DEFAULT_PERMISSIVE_MODE); + JSONObject root1 = (JSONObject) parser.parseNext(JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("1212", root1.get("tranid")); + + int count = 3; + while (parser.hasNext()) { + Transaction root2 = parser.parseNext(Transaction.class); + Assertions.assertEquals("121" + count, root2.getTranid()); + Assertions.assertEquals("34" + count, root2.getUser().getName()); + count++; + } + Assertions.assertEquals(7, count); + } + + @Test + public void testMultipleJsonsFromInvalidStringFailed() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}} bbb " + + "\n{tranid:\"1213\", \"user\":{\"name\":\"343\",\"addr\":\"4233 iu\"}} "; + MultipleJsonParser parser = new MultipleJsonParser(json, DEFAULT_PERMISSIVE_MODE); + JSONObject root1 = (JSONObject) parser.parseNext(JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("1212", root1.get("tranid")); + + int count = 3; + while (parser.hasNext()) { + // return invalid input when parse failed + Object root2 = parser.parseNext(JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals( + "bbb \n" + "{tranid:\"1213\", \"user\":{\"name\":\"343\",\"addr\":\"4233 iu\"}}", root2); + count++; + } + Assertions.assertEquals(4, count); + } + + @Test + public void testMultipleJsonsWithJsonParserBySubStringSuccess() throws Exception { + String json = + "{tranid:\"1212\", \"user\":{\"name\":\"123\",\"addr\":\"786 rt\"}}" + + "\n{tranid:\"1213\", \"user\":{\"name\":\"345\",\"addr\":\"4234 iu\"}}"; + JSONParser parser = new JSONParser(DEFAULT_PERMISSIVE_MODE); + JSONObject root1 = (JSONObject) parser.parse(json, JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("1212", root1.get("tranid")); + + Field field = Class.forName("net.minidev.json.parser.JSONParserBase").getDeclaredField("pos"); + field.setAccessible(true); + Field pStringField = JSONParser.class.getDeclaredField("pString"); + pStringField.setAccessible(true); + Object parser2 = pStringField.get(parser); + Integer ipos = (Integer) field.get(parser2); + Assertions.assertEquals(54, ipos); + + JSONObject root2 = + (JSONObject) parser.parse(json.substring(ipos), JSONValue.defaultReader.DEFAULT); + Assertions.assertEquals("1213", root2.get("tranid")); + ipos = (Integer) field.get(parser2); + Assertions.assertEquals(56, ipos); + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/parser/Transaction.java b/json-smart/src/test/java/net/minidev/json/test/parser/Transaction.java new file mode 100644 index 0000000..e8c9db1 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/parser/Transaction.java @@ -0,0 +1,77 @@ +package net.minidev.json.test.parser; + +import java.util.List; +import java.util.Map; + +/** Represents a transaction data structure parsed from JSON. */ +public class Transaction { + private String tranid; + private User user; + private List> friends; + + // Getters and Setters + public String getTranid() { + return tranid; + } + + public void setTranid(String tranid) { + this.tranid = tranid; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public List> getFriends() { + return friends; + } + + public void setFriends(List> friends) { + this.friends = friends; + } + + @Override + public String toString() { + return "Transaction{" + + "tranid='" + + tranid + + '\'' + + ", user=" + + user + + ", friends=" + + friends + + '}'; + } + + /** Represents the user object within the transaction. */ + public static class User { + private String name; + private String addr; + + // Getters and Setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddr() { + return addr; + } + + public void setAddr(String addr) { + this.addr = addr; + } + + @Override + public String toString() { + return "User{" + "name='" + name + '\'' + ", addr='" + addr + '\'' + '}'; + } + } +}