From f70cd9e7db6551b15e780ffd8a78492a29d96031 Mon Sep 17 00:00:00 2001 From: urielch Date: Tue, 25 Mar 2025 03:54:52 +0100 Subject: [PATCH 1/6] Add support for incomplete JSON parsing and enhance documentation - Introduced ACCEPT_INCOMPLETE mode in JSONParser to allow parsing of incomplete JSON data. - Updated JSONParserBase and related classes to handle incomplete data scenarios. - Added unit tests for various incomplete JSON cases in JSONIncompletModeTest. --- .../main/java/net/minidev/json/JSONArray.java | 26 ++++++- .../net/minidev/json/parser/JSONParser.java | 22 +++++- .../minidev/json/parser/JSONParserBase.java | 34 ++++++++- .../json/parser/JSONParserByteArray.java | 1 + .../minidev/json/parser/JSONParserMemory.java | 8 ++- .../minidev/json/parser/JSONParserReader.java | 7 +- .../minidev/json/parser/JSONParserString.java | 1 + .../json/test/JSONIncompletModeTest.java | 72 +++++++++++++++++++ 8 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java diff --git a/json-smart/src/main/java/net/minidev/json/JSONArray.java b/json-smart/src/main/java/net/minidev/json/JSONArray.java index 54c2b72..444d2c0 100644 --- a/json-smart/src/main/java/net/minidev/json/JSONArray.java +++ b/json-smart/src/main/java/net/minidev/json/JSONArray.java @@ -76,13 +76,21 @@ public static void writeJSONString( JsonWriter.JSONIterableWriter.writeJSONString(list, out, compression); } + /** + * Encode a list into JSON text and write it to out. If this list is also a JSONStreamAware or a + * JSONAware, JSONStreamAware and JSONAware specific behaviours will be ignored at this top level. + * + * @param list + * @param out + * @throws IOException + */ public static void writeJSONString(List list, Appendable out) throws IOException { writeJSONString(list, out, JSONValue.COMPRESSION); } /** - * Appends the specified element and returns this. Handy alternative to add(E e) method. + * Appends the specified element and returns this. same effect that add(E e) method. * * @param element element to be appended to this array. * @return this @@ -92,6 +100,11 @@ public JSONArray appendElement(Object element) { return this; } + /** + * Merges the specified object into this array. can trigger an add(E e) or addAll(E e) method. + * + * @param o2 + */ public void merge(Object o2) { JSONObject.merge(this, o2); } @@ -119,10 +132,21 @@ public String toString(JSONStyle compression) { return toJSONString(compression); } + /** + * JSONStreamAwareEx interface + * + * @param out output stream + */ public void writeJSONString(Appendable out) throws IOException { writeJSONString(this, out, JSONValue.COMPRESSION); } + /** + * JSONStreamAwareEx interface + * + * @param out output stream + * @param compression compression param + */ public void writeJSONString(Appendable out, JSONStyle compression) throws IOException { writeJSONString(this, out, compression); } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java index bb93f06..f56e69a 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java @@ -101,11 +101,29 @@ public class JSONParser { public static final int LIMIT_JSON_DEPTH = 4096; /** - * smart mode, fastest parsing mode. accept lots of non standard json syntax + * If the parser is in stream mode + * + *

Stream mode is used to parse a stream of json data. It will not throw exception on + * incomplete data. + * + * @since 2.6 + */ + public static final int ACCEPT_INCOMPLETE = 8192; + + /** + * smart mode, fastest parsing mode. accept lots of non standard json syntax ACCEPT_INCOMPLETE + * feature is not enabled. in this mode, for backward compatibility * * @since 1.0.6 */ - public static final int MODE_PERMISSIVE = -1; + public static final int MODE_PERMISSIVE = -1 & ~ACCEPT_INCOMPLETE; + + /* + * smart mode, fastest parsing mode. accept lots of non standard json syntax + * ACCEPT_INCOMPLETE feature is enabled. + * @since 2.6 + */ + public static final int MODE_PERMISSIVE_NEW = -1; /** * strict RFC4627 mode. 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..b74a625 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 @@ -89,6 +89,7 @@ abstract class JSONParserBase { protected final boolean reject127; protected final boolean unrestictBigDigit; protected final boolean limitJsonDepth; + protected final boolean acceptIncomplet; public JSONParserBase(int permissiveMode) { this.acceptNaN = (permissiveMode & JSONParser.ACCEPT_NAN) > 0; @@ -106,6 +107,7 @@ public JSONParserBase(int permissiveMode) { this.reject127 = (permissiveMode & JSONParser.REJECT_127_CHAR) > 0; this.unrestictBigDigit = (permissiveMode & JSONParser.BIG_DIGIT_UNRESTRICTED) > 0; this.limitJsonDepth = (permissiveMode & JSONParser.LIMIT_JSON_DEPTH) > 0; + this.acceptIncomplet = (permissiveMode & JSONParser.ACCEPT_INCOMPLETE) > 0; } public void checkControleChar() throws ParseException { @@ -313,6 +315,10 @@ protected T readArray(JsonReaderI mapper) throws ParseException, IOExcept needData = true; continue; case EOI: + if (acceptIncomplet) { + this.depth--; + return mapper.convert(current); + } throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); default: mapper.addValue(current, readMain(mapper, stopArray)); @@ -491,6 +497,11 @@ protected Object readMain(JsonReaderI mapper, boolean stop[]) if (!acceptNonQuote) throw new ParseException(pos, ERROR_UNEXPECTED_TOKEN, xs); // return xs; + case EOI: + if (acceptIncomplet) { + return null; + } + throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); // digits case '0': case '1': @@ -527,6 +538,7 @@ protected T readObject(JsonReaderI mapper) throws ParseException, IOExcep if (limitJsonDepth && ++this.depth > MAX_DEPTH) { throw new ParseException(pos, ERROR_UNEXPECTED_JSON_DEPTH, c); } + // Store the appropriate read method in a variable Object current = mapper.createObject(); boolean needData = false; boolean acceptData = true; @@ -572,7 +584,14 @@ protected T readObject(JsonReaderI mapper) throws ParseException, IOExcep skipSpace(); if (c != ':') { - if (c == EOI) throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null); + if (c == EOI) { + if (acceptIncomplet) { + this.depth--; + mapper.setValue(current, key, null); + return mapper.convert(current); + } + throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null); + } throw new ParseException(pos - 1, ERROR_UNEXPECTED_CHAR, c); } readNoEnd(); /* skip : */ @@ -593,8 +612,13 @@ protected T readObject(JsonReaderI mapper) throws ParseException, IOExcep // return mapper.convert(current); } - if (c == EOI) // Fixed on 18/10/2011 reported by vladimir - throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null); + if (c == EOI) { // Fixed on 18/10/2011 reported by vladimir + if (acceptIncomplet) { + this.depth--; + return mapper.convert(current); + } + throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null); + } // if c==, continue if (c == ',') acceptData = needData = true; else throw new ParseException(pos - 1, ERROR_UNEXPECTED_TOKEN, c); @@ -615,6 +639,10 @@ protected void readString2() throws ParseException, IOException { read(); switch (c) { case EOI: + if (acceptIncomplet) { + xs = sb.toString(); + return; + } throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null); case '"': case '\'': 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..44a87fc 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 @@ -96,6 +96,7 @@ protected void readS() { protected void readNoEnd() throws ParseException { if (++pos >= len) { this.c = EOI; + if (super.acceptIncomplet) return; throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); } else this.c = (char) in[pos]; } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java index edf9d8d..210adda 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java @@ -113,7 +113,13 @@ protected void readString() throws ParseException, IOException { throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c); } int tmpP = indexOf(c, pos + 1); - if (tmpP == -1) throw new ParseException(len, ERROR_UNEXPECTED_EOF, null); + if (tmpP == -1) { + if (acceptIncomplet) { + readString2(); + return; + } + throw new ParseException(len, ERROR_UNEXPECTED_EOF, null); + } extractString(pos + 1, tmpP); if (xs.indexOf('\\') == -1) { checkControleChar(); 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..fdb988a 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 @@ -82,8 +82,11 @@ protected void readS() throws IOException { protected void readNoEnd() throws ParseException, IOException { int i = in.read(); - if (i == -1) throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); + if (i == -1) { + this.c = EOI; + if (super.acceptIncomplet) return; + throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); + } c = (char) i; - // } } 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..15f4f5e 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 @@ -93,6 +93,7 @@ protected void readS() { protected void readNoEnd() throws ParseException { if (++pos >= len) { this.c = EOI; + if (super.acceptIncomplet) return; throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); } else this.c = in.charAt(pos); } diff --git a/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java b/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java new file mode 100644 index 0000000..ce806a5 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java @@ -0,0 +1,72 @@ +package net.minidev.json.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import org.junit.jupiter.api.Test; + +/** + * TODO make the same tests in stream and bytes mode + */ + +public class JSONIncompletModeTest { + @Test + public void testArraySimple() throws Exception { + String s = "[1"; + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONArray array = (JSONArray) p.parse(s); + assertEquals(Long.valueOf(1), (Long) array.get(0)); + } + + @Test + public void testArrayInObject1() throws Exception { + String s = "{\"obj\":[1"; + String result = "{\"obj\":[1]}"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONObject array = (JSONObject) p.parse(s); + assertEquals(result, array.toJSONString()); + } + + @Test + public void testObjectCut() throws Exception { + String s = "{\"obj\":"; + String result = "{\"obj\":null}"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONObject array = (JSONObject) p.parse(s); + assertEquals(result, array.toJSONString()); + } + + @Test + public void testObjectCut2() throws Exception { + String s = "{\"obj\""; + String result = "{\"obj\":null}"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONObject array = (JSONObject) p.parse(s); + assertEquals(result, array.toJSONString()); + } + + @Test + public void testObjectCut3() throws Exception { + String s = "{\"obj"; + String result = "{\"obj\":null}"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONObject array = (JSONObject) p.parse(s); + assertEquals(result, array.toJSONString()); + } + + @Test + public void testObjectCut4() throws Exception { + String s = "{\"obj\":\""; + String result = "{\"obj\":\"\"}"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + JSONObject array = (JSONObject) p.parse(s); + assertEquals(result, array.toJSONString()); + } +} From eebc79abaa9a339173d938cc31cc4f78b145e761 Mon Sep 17 00:00:00 2001 From: urielch Date: Tue, 25 Mar 2025 13:18:38 +0100 Subject: [PATCH 2/6] Enhance test coverage for JSONArray, JSONObject, JSONUtil, and JSONValue --- README.md | 1 + .../main/java/net/minidev/json/JSONArray.java | 3 +- .../main/java/net/minidev/json/JSONUtil.java | 22 ++- .../json/test/JSONIncompletModeTest.java | 16 ++- .../json/test/unit/TestJSONArrayTest.java | 88 ++++++++++++ .../json/test/unit/TestJSONObjectTest.java | 131 ++++++++++++++++++ .../minidev/json/test/unit/TestJSONUtil.java | 89 ++++++++++++ .../minidev/json/test/unit/TestJSONValue.java | 61 ++++++++ 8 files changed, 399 insertions(+), 12 deletions(-) create mode 100644 json-smart/src/test/java/net/minidev/json/test/unit/TestJSONArrayTest.java create mode 100644 json-smart/src/test/java/net/minidev/json/test/unit/TestJSONObjectTest.java create mode 100644 json-smart/src/test/java/net/minidev/json/test/unit/TestJSONUtil.java create mode 100644 json-smart/src/test/java/net/minidev/json/test/unit/TestJSONValue.java diff --git a/README.md b/README.md index 4b70e78..669c1e4 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ So I do not use my json-smart anymore. I had fun with this project. If you want * JSONObject merge support overwrite as parameter. [PR 238](https://github.com/netplex/json-smart-v2/pull/238) + ### *V 2.5.2* (2025-02-12) * Fix CVE-2024-57699 for predefined parsers. [PR 233](https://github.com/netplex/json-smart-v2/pull/233) diff --git a/json-smart/src/main/java/net/minidev/json/JSONArray.java b/json-smart/src/main/java/net/minidev/json/JSONArray.java index 444d2c0..41c5413 100644 --- a/json-smart/src/main/java/net/minidev/json/JSONArray.java +++ b/json-smart/src/main/java/net/minidev/json/JSONArray.java @@ -26,8 +26,7 @@ * @author FangYidong <fangyidong@yahoo.com.cn> * @author Uriel Chemouni <uchemouni@gmail.com> */ -public class JSONArray extends ArrayList - implements List, JSONAwareEx, JSONStreamAwareEx { +public class JSONArray extends ArrayList implements JSONAwareEx, JSONStreamAwareEx { private static final long serialVersionUID = 9106884089231309568L; public JSONArray() {} diff --git a/json-smart/src/main/java/net/minidev/json/JSONUtil.java b/json-smart/src/main/java/net/minidev/json/JSONUtil.java index 4185507..8a1fe4e 100644 --- a/json-smart/src/main/java/net/minidev/json/JSONUtil.java +++ b/json-smart/src/main/java/net/minidev/json/JSONUtil.java @@ -45,10 +45,11 @@ else if (dest == double.class) if (obj instanceof Number) return ((Number) obj).doubleValue(); else return Double.valueOf(obj.toString()); else if (dest == char.class) { - String asString = dest.toString(); + String asString = obj.toString(); if (asString.length() > 0) return Character.valueOf(asString.charAt(0)); } else if (dest == boolean.class) { - return (Boolean) obj; + if (obj instanceof Number) return ((Number) obj).intValue() != 0; + if (obj instanceof String) return Boolean.valueOf((String) obj); } throw new RuntimeException( "Primitive: Can not convert " + obj.getClass().getName() + " to " + dest.getName()); @@ -73,9 +74,13 @@ else if (dest == char.class) { if (obj instanceof Number) return Double.valueOf(((Number) obj).doubleValue()); else return Double.valueOf(obj.toString()); if (dest == Character.class) { - String asString = dest.toString(); + String asString = obj.toString(); if (asString.length() > 0) return Character.valueOf(asString.charAt(0)); } + if (dest == Boolean.class) { + if (obj instanceof Number) return ((Number) obj).intValue() != 0; + if (obj instanceof String) return Boolean.valueOf((String) obj); + } throw new RuntimeException( "Object: Can not Convert " + obj.getClass().getName() + " to " + dest.getName()); } @@ -94,10 +99,11 @@ public static Object convertToX(Object obj, Class dest) { else if (dest == float.class) return Float.valueOf(obj.toString()); else if (dest == double.class) return Double.valueOf(obj.toString()); else if (dest == char.class) { - String asString = dest.toString(); + String asString = obj.toString(); if (asString.length() > 0) return Character.valueOf(asString.charAt(0)); } else if (dest == boolean.class) { - return (Boolean) obj; + if (obj instanceof Number) return ((Number) obj).intValue() != 0; + if (obj instanceof String) return Boolean.valueOf((String) obj); } throw new RuntimeException( "Primitive: Can not convert " + obj.getClass().getName() + " to " + dest.getName()); @@ -122,9 +128,13 @@ else if (dest == char.class) { if (obj instanceof Number) return Double.valueOf(((Number) obj).doubleValue()); else return Double.valueOf(obj.toString()); if (dest == Character.class) { - String asString = dest.toString(); + String asString = obj.toString(); if (asString.length() > 0) return Character.valueOf(asString.charAt(0)); } + if (dest == Boolean.class) { + if (obj instanceof Number) return ((Number) obj).intValue() != 0; + if (obj instanceof String) return Boolean.valueOf((String) obj); + } throw new RuntimeException( "Object: Can not Convert " + obj.getClass().getName() + " to " + dest.getName()); } diff --git a/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java b/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java index ce806a5..92a40ce 100644 --- a/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/JSONIncompletModeTest.java @@ -7,11 +7,9 @@ import net.minidev.json.parser.JSONParser; import org.junit.jupiter.api.Test; -/** - * TODO make the same tests in stream and bytes mode - */ - +/** TODO make the same tests in stream and bytes mode */ public class JSONIncompletModeTest { + @Test public void testArraySimple() throws Exception { String s = "[1"; @@ -69,4 +67,14 @@ public void testObjectCut4() throws Exception { JSONObject array = (JSONObject) p.parse(s); assertEquals(result, array.toJSONString()); } + + @Test + public void testStringCut() throws Exception { + String s = "\"obj"; + String result = "obj"; + + JSONParser p = new JSONParser(JSONParser.MODE_JSON_SIMPLE | JSONParser.ACCEPT_INCOMPLETE); + String array = (String) p.parse(s); + assertEquals(result, array); + } } diff --git a/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONArrayTest.java b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONArrayTest.java new file mode 100644 index 0000000..baeede1 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONArrayTest.java @@ -0,0 +1,88 @@ +package net.minidev.json.test.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONStyle; +import org.junit.jupiter.api.Test; + +/** Test serialization of JSONArray to max coverage increase JSONArray coverage from 47% to 100% */ +public class TestJSONArrayTest { + + @Test + public void testAlloc() { + // test default constructor + JSONArray a = new JSONArray(12); + assertEquals(0, a.size()); + } + + @Test + public void testToJSONStringWithEmptyList() { + String jsonString = JSONArray.toJSONString(new ArrayList<>()); + assertEquals("[]", jsonString); + } + + @Test + public void testAppendElement() throws IOException { + JSONArray a = new JSONArray(1); + a.appendElement(1); + assertEquals("[1]", a.toJSONString(JSONStyle.MAX_COMPRESS)); + + assertEquals("[1]", a.toString()); + + assertEquals("[1]", a.toString(JSONStyle.MAX_COMPRESS)); + + StringBuilder sb = new StringBuilder(); + a.writeJSONString(sb); + assertEquals("[1]", sb.toString()); + + sb = new StringBuilder(); + JSONArray.writeJSONString(Arrays.asList(1), sb); + + sb = new StringBuilder(); + JSONArray.writeJSONString(null, sb, JSONStyle.MAX_COMPRESS); + assertEquals("null", sb.toString()); + } + + @Test + public void testToJSONStringWithSingleElement() { + List list = Arrays.asList(1); + String jsonString = JSONArray.toJSONString(list); + assertEquals("[1]", jsonString); + } + + @Test + public void testToJSONStringWithMultipleElements() { + List list = Arrays.asList(1, "two", 3.0); + String jsonString = JSONArray.toJSONString(list); + assertEquals("[1,\"two\",3.0]", jsonString); + } + + @Test + public void testWriteJSONStringWithEmptyList() throws Exception { + StringWriter out = new StringWriter(); + JSONArray.writeJSONString(new ArrayList<>(), out); + assertEquals("[]", out.toString()); + } + + @Test + public void testWriteJSONStringWithSingleElement() throws Exception { + StringWriter out = new StringWriter(); + List list = Arrays.asList(1); + JSONArray.writeJSONString(list, out); + assertEquals("[1]", out.toString()); + } + + @Test + public void testWriteJSONStringWithMultipleElements() throws Exception { + StringWriter out = new StringWriter(); + List list = Arrays.asList(1, "two", 3.0); + JSONArray.writeJSONString(list, out); + assertEquals("[1,\"two\",3.0]", out.toString()); + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONObjectTest.java b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONObjectTest.java new file mode 100644 index 0000000..92fa2db --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONObjectTest.java @@ -0,0 +1,131 @@ +package net.minidev.json.test.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONStyle; +import org.junit.jupiter.api.Test; + +/** + * Test serialization of JSONObject to max coverage increase JSONObject coverage from 64% to 100% + * + * @see JSONObject + */ +public class TestJSONObjectTest { + + @Test + public void testEscape() { + String escaped = JSONObject.escape("\"test\""); + assertEquals("\\\"test\\\"", escaped); + } + + @Test + public void testToJSONStringWithEmptyMap() { + String jsonString = JSONObject.toJSONString(new HashMap<>()); + assertEquals("{}", jsonString); + } + + @Test + public void testToJSONStringWithSingleElement() { + Map map = new HashMap<>(); + map.put("key", "value"); + String jsonString = JSONObject.toJSONString(map); + assertEquals("{\"key\":\"value\"}", jsonString); + } + + @Test + public void testToJSONStringWithMultipleElements() { + Map map = new HashMap<>(); + map.put("key1", 1); + map.put("key2", "two"); + map.put("key3", 3.0); + String jsonString = JSONObject.toJSONString(map); + assertEquals("{\"key1\":1,\"key2\":\"two\",\"key3\":3.0}", jsonString); + } + + @Test + public void testWriteJSONStringWithSingleElement() throws IOException { + JSONObject map = new JSONObject(1); + map.put("key", "value"); + String txt = map.toJSONString(); + assertEquals("{\"key\":\"value\"}", txt); + } + + // @Test + // public void testWriteJSONStringWithMultipleElements() throws IOException { + // StringWriter out = new StringWriter(); + // Map map = new HashMap<>(); + // map.put("key1", 1); + // map.put("key2", "two"); + // map.put("key3", 3.0); + // JSONObject.writeJSONString(map, out); + // assertEquals("{\"key1\":1,\"key2\":\"two\",\"key3\":3.0}", out.toString()); + // } + + @Test + public void testAppendField() { + JSONObject obj = new JSONObject(); + obj.appendField("key", "value"); + assertEquals("value", obj.get("key")); + } + + @Test + public void testGetAsString() { + JSONObject obj = new JSONObject(); + obj.put("key", "value"); + assertEquals("value", obj.getAsString("key")); + } + + @Test + public void testGetAsNumber() { + JSONObject obj = new JSONObject(); + obj.put("key", 123); + assertEquals(123, obj.getAsNumber("key")); + } + + @Test + public void testMerge() { + JSONObject obj1 = new JSONObject(); + obj1.put("key1", 1); + JSONObject obj2 = new JSONObject(); + obj2.put("key2", "two"); + obj1.merge(obj2); + assertEquals(1, obj1.get("key1")); + assertEquals("two", obj1.get("key2")); + } + + @Test + public void testMergeWithOverwrite() { + JSONObject obj1 = new JSONObject(); + obj1.put("key", 1); + JSONObject obj2 = new JSONObject(); + obj2.put("key", 2); + obj1.merge(obj2, true); + assertEquals(2, obj1.get("key")); + } + + @Test + public void testMergeWithOverwrite1() { + JSONObject obj1 = new JSONObject(); + obj1.put("key", 1); + obj1.merge(null, true); + assertEquals(1, obj1.get("key")); + } + + @Test + public void testToString() { + JSONObject obj = new JSONObject(); + obj.put("key", "value"); + assertEquals("{\"key\":\"value\"}", obj.toString()); + } + + @Test + public void testWriteJSONNull() throws IOException { + StringBuilder sb = new StringBuilder(); + JSONObject.writeJSON(null, sb, JSONStyle.MAX_COMPRESS); + assertEquals("null", sb.toString()); + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONUtil.java b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONUtil.java new file mode 100644 index 0000000..651ee42 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONUtil.java @@ -0,0 +1,89 @@ +package net.minidev.json.test.unit; + +import static org.junit.jupiter.api.Assertions.*; + +import net.minidev.json.JSONUtil; +import org.junit.jupiter.api.Test; + +/** + * improve coverage for + * + * @see JSONUtil + */ +public class TestJSONUtil { + + @Test + public void testConvertToStrictNumbers() { + assertEquals(1, JSONUtil.convertToStrict(1, Number.class)); + assertEquals(null, JSONUtil.convertToStrict(null, byte.class)); + assertEquals((byte) 1, JSONUtil.convertToStrict(Byte.valueOf((byte) 1), byte.class)); + assertEquals((short) 1, JSONUtil.convertToStrict(Short.valueOf((short) 1), short.class)); + + assertEquals((byte) 1, JSONUtil.convertToStrict("1", byte.class)); + assertEquals((short) 1, JSONUtil.convertToStrict("1", short.class)); + assertEquals(1, JSONUtil.convertToStrict("1", int.class)); + assertEquals(1L, JSONUtil.convertToStrict("1", long.class)); + assertEquals(1.0f, JSONUtil.convertToStrict("1", float.class)); + assertEquals(1.0, JSONUtil.convertToStrict("1", double.class)); + assertEquals((byte) 1, JSONUtil.convertToStrict("1", Byte.class)); + assertEquals((short) 1L, JSONUtil.convertToStrict("1", Short.class)); + assertEquals(1, JSONUtil.convertToStrict("1", Integer.class)); + assertEquals(1L, JSONUtil.convertToStrict("1", Long.class)); + assertEquals(1.0f, JSONUtil.convertToStrict("1", Float.class)); + assertEquals(1.0, JSONUtil.convertToStrict("1", Double.class)); + } + + @Test + public void testConvertToStrictX() { + assertEquals('a', JSONUtil.convertToStrict("a", char.class)); + assertEquals(true, JSONUtil.convertToStrict("true", boolean.class)); + assertEquals('a', JSONUtil.convertToStrict("a", Character.class)); + assertEquals(true, JSONUtil.convertToStrict("true", Boolean.class)); + } + + @Test + public void testConvertToNumbers() { + assertEquals((byte) 1, JSONUtil.convertToX("1", byte.class)); + assertEquals((short) 1, JSONUtil.convertToX("1", short.class)); + assertEquals(1, JSONUtil.convertToX("1", int.class)); + assertEquals(1L, JSONUtil.convertToX("1", long.class)); + assertEquals(1.0f, JSONUtil.convertToX("1", float.class)); + assertEquals(1.0, JSONUtil.convertToX("1", double.class)); + assertEquals((byte) 1, JSONUtil.convertToX("1", Byte.class)); + assertEquals((short) 1, JSONUtil.convertToX("1", Short.class)); + assertEquals(1, JSONUtil.convertToX("1", Integer.class)); + assertEquals(1L, JSONUtil.convertToX("1", Long.class)); + assertEquals(1.0f, JSONUtil.convertToX("1", Float.class)); + assertEquals(1.0, JSONUtil.convertToX("1", Double.class)); + } + + @Test + public void testConvertToX() { + assertEquals('a', JSONUtil.convertToX("a", char.class)); + assertEquals(null, JSONUtil.convertToX(null, char.class)); + assertEquals(true, JSONUtil.convertToX("true", boolean.class)); + + assertEquals('a', JSONUtil.convertToX("a", Character.class)); + assertEquals(true, JSONUtil.convertToX("true", Boolean.class)); + } + + @Test + public void testGetSetterName() { + assertEquals("setKey", JSONUtil.getSetterName("key")); + } + + @Test + public void testGetGetterName() { + assertEquals("getKey", JSONUtil.getGetterName("key")); + } + + @Test + public void testGetIsName() { + assertEquals("isKey", JSONUtil.getIsName("key")); + } + + @Test + public void testGetIsName2() { + assertEquals("isKey", JSONUtil.getIsName("Key")); + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONValue.java b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONValue.java new file mode 100644 index 0000000..1612004 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/unit/TestJSONValue.java @@ -0,0 +1,61 @@ +package net.minidev.json.test.unit; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringReader; +import net.minidev.json.JSONValue; +import org.junit.jupiter.api.Test; + +/** + * improve coverage for + * + * @see JSONValue + */ +public class TestJSONValue { + + static void Parse4Times(String text, Object expected) { + Object ret = JSONValue.parse(text); + assertEquals(ret, expected); + + byte[] b = text.getBytes(); + ret = JSONValue.parse(b); + assertEquals(ret, expected); + + InputStream stream = new ByteArrayInputStream(b); + ret = JSONValue.parse(stream); + assertEquals(ret, expected); + + StringReader reader = new StringReader(text); + ret = JSONValue.parse(reader); + assertEquals(ret, expected); + } + + static void Parse4TimesAs(String text, Object expected, Class toClass) { + Object ret = JSONValue.parse(text, toClass); + assertEquals(ret, expected); + + byte[] b = text.getBytes(); + ret = JSONValue.parse(b, toClass); + assertEquals(ret, expected); + + InputStream stream = new ByteArrayInputStream(b); + ret = JSONValue.parse(stream, toClass); + assertEquals(ret, expected); + + StringReader reader = new StringReader(text); + ret = JSONValue.parse(reader, toClass); + assertEquals(ret, expected); + } + + /** all error are dropped as null */ + @Test + public void testParseHideErrorString() { + Parse4Times("{\"key\"", null); + Parse4Times("\"key\"", "key"); + + Parse4TimesAs("{\"key\"", null, String.class); + Parse4TimesAs("\"key\"", "key", String.class); + } +} From 668c0a85e3953c6d3a783b1c1c80eeb91e704f04 Mon Sep 17 00:00:00 2001 From: urielch Date: Tue, 25 Mar 2025 15:01:48 +0100 Subject: [PATCH 3/6] Add JSONParser.ACCEPT_INCOMPLETE to support parsing of partial JSON in changelog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 669c1e4..3591d97 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ So I do not use my json-smart anymore. I had fun with this project. If you want ### *V 2.6.0* (next version) * JSONObject merge support overwrite as parameter. [PR 238](https://github.com/netplex/json-smart-v2/pull/238) - +* Add `JSONParser.ACCEPT_INCOMPLETE` to allow parsing partial and incomplete JSON without error [PR 254](https://github.com/netplex/json-smart-v2/pull/254) ### *V 2.5.2* (2025-02-12) From 38b7ec5099673495870529ed977739f226b1694a Mon Sep 17 00:00:00 2001 From: urielch Date: Tue, 25 Mar 2025 15:07:06 +0100 Subject: [PATCH 4/6] add a precommit script --- precommit.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 precommit.sh diff --git a/precommit.sh b/precommit.sh new file mode 100755 index 0000000..629cfc0 --- /dev/null +++ b/precommit.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +cd json-smart +mvn spotless:check; +cd .. + +cd accessors-smart +mvn spotless:check +cd .. + +cd json-smart-action +mvn spotless:check +cd .. From be5a5bfb81f63258bd630a591bc68247423d321b Mon Sep 17 00:00:00 2001 From: urielch Date: Wed, 26 Mar 2025 07:06:46 +0100 Subject: [PATCH 5/6] RENAME acceptIncomplet to acceptIncomplete --- .../net/minidev/json/parser/JSONParserBase.java | 14 +++++++------- .../minidev/json/parser/JSONParserByteArray.java | 2 +- .../net/minidev/json/parser/JSONParserMemory.java | 2 +- .../net/minidev/json/parser/JSONParserReader.java | 2 +- .../net/minidev/json/parser/JSONParserString.java | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) 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 b74a625..227dbb9 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 @@ -89,7 +89,7 @@ abstract class JSONParserBase { protected final boolean reject127; protected final boolean unrestictBigDigit; protected final boolean limitJsonDepth; - protected final boolean acceptIncomplet; + protected final boolean acceptIncomplete; public JSONParserBase(int permissiveMode) { this.acceptNaN = (permissiveMode & JSONParser.ACCEPT_NAN) > 0; @@ -107,7 +107,7 @@ public JSONParserBase(int permissiveMode) { this.reject127 = (permissiveMode & JSONParser.REJECT_127_CHAR) > 0; this.unrestictBigDigit = (permissiveMode & JSONParser.BIG_DIGIT_UNRESTRICTED) > 0; this.limitJsonDepth = (permissiveMode & JSONParser.LIMIT_JSON_DEPTH) > 0; - this.acceptIncomplet = (permissiveMode & JSONParser.ACCEPT_INCOMPLETE) > 0; + this.acceptIncomplete = (permissiveMode & JSONParser.ACCEPT_INCOMPLETE) > 0; } public void checkControleChar() throws ParseException { @@ -315,7 +315,7 @@ protected T readArray(JsonReaderI mapper) throws ParseException, IOExcept needData = true; continue; case EOI: - if (acceptIncomplet) { + if (acceptIncomplete) { this.depth--; return mapper.convert(current); } @@ -498,7 +498,7 @@ protected Object readMain(JsonReaderI mapper, boolean stop[]) // return xs; case EOI: - if (acceptIncomplet) { + if (acceptIncomplete) { return null; } throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); @@ -585,7 +585,7 @@ protected T readObject(JsonReaderI mapper) throws ParseException, IOExcep if (c != ':') { if (c == EOI) { - if (acceptIncomplet) { + if (acceptIncomplete) { this.depth--; mapper.setValue(current, key, null); return mapper.convert(current); @@ -613,7 +613,7 @@ protected T readObject(JsonReaderI mapper) throws ParseException, IOExcep return mapper.convert(current); } if (c == EOI) { // Fixed on 18/10/2011 reported by vladimir - if (acceptIncomplet) { + if (acceptIncomplete) { this.depth--; return mapper.convert(current); } @@ -639,7 +639,7 @@ protected void readString2() throws ParseException, IOException { read(); switch (c) { case EOI: - if (acceptIncomplet) { + if (acceptIncomplete) { xs = sb.toString(); return; } 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 44a87fc..29121dc 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 @@ -96,7 +96,7 @@ protected void readS() { protected void readNoEnd() throws ParseException { if (++pos >= len) { this.c = EOI; - if (super.acceptIncomplet) return; + if (super.acceptIncomplete) return; throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); } else this.c = (char) in[pos]; } diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java index 210adda..bb19984 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserMemory.java @@ -114,7 +114,7 @@ protected void readString() throws ParseException, IOException { } int tmpP = indexOf(c, pos + 1); if (tmpP == -1) { - if (acceptIncomplet) { + if (acceptIncomplete) { readString2(); return; } 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 fdb988a..20f624e 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 @@ -84,7 +84,7 @@ protected void readNoEnd() throws ParseException, IOException { int i = in.read(); if (i == -1) { this.c = EOI; - if (super.acceptIncomplet) return; + if (super.acceptIncomplete) return; throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); } c = (char) i; 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 15f4f5e..94a4c78 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 @@ -93,7 +93,7 @@ protected void readS() { protected void readNoEnd() throws ParseException { if (++pos >= len) { this.c = EOI; - if (super.acceptIncomplet) return; + if (super.acceptIncomplete) return; throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF"); } else this.c = in.charAt(pos); } From 6d28723e7a85606842081d0e452d9b5ae2a9bcf6 Mon Sep 17 00:00:00 2001 From: urielch Date: Wed, 26 Mar 2025 07:10:02 +0100 Subject: [PATCH 6/6] rename MODE_PERMISSIVE_NEW to MODE_PERMISSIVE_WITH_INCOMPLETE --- .../src/main/java/net/minidev/json/parser/JSONParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java index f56e69a..08584a7 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParser.java @@ -123,7 +123,7 @@ public class JSONParser { * ACCEPT_INCOMPLETE feature is enabled. * @since 2.6 */ - public static final int MODE_PERMISSIVE_NEW = -1; + public static final int MODE_PERMISSIVE_WITH_INCOMPLETE = -1; /** * strict RFC4627 mode.