From d1327c2da312d1670ecf9b9170cd36641e2522dd Mon Sep 17 00:00:00 2001 From: Robert Lichtenberger Date: Wed, 19 Mar 2025 07:59:57 +0100 Subject: [PATCH 1/4] Allow to configure Java null handling. --- src/main/java/org/json/JSONObject.java | 2 +- .../org/json/JSONParserConfiguration.java | 31 ++++++++++++++ .../java/org/json/junit/JSONObjectTest.java | 40 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index d50fff7..73835d4 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -332,7 +332,7 @@ public class JSONObject { throw new NullPointerException("Null key."); } final Object value = e.getValue(); - if (value != null) { + if (value != null || jsonParserConfiguration.isJavaNullAsJsonNull()) { testValidity(value); this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration)); } diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index 3fbfb8a..58d080f 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -8,6 +8,11 @@ public class JSONParserConfiguration extends ParserConfiguration { * Used to indicate whether to overwrite duplicate key or not. */ private boolean overwriteDuplicateKey; + + /** + * Used to indicate whether ignore null values when converting java maps to JSONObject or not. + */ + private boolean javaNullAsJsonNull; /** * Configuration with the default values. @@ -67,6 +72,21 @@ public class JSONParserConfiguration extends ParserConfiguration { return clone; } + + /** + * Controls the parser's behavior when meeting duplicate keys. + * If set to false, the parser will throw a JSONException when meeting a duplicate key. + * Or the duplicate key's value will be overwritten. + * + * @param javaNullAsJsonNull define, if the parser should ignore null values in Java maps + * @return The existing configuration will not be modified. A new configuration is returned. + */ + public JSONParserConfiguration withJavaNullAsJsonNull(final boolean javaNullAsJsonNull) { + JSONParserConfiguration clone = this.clone(); + clone.javaNullAsJsonNull = javaNullAsJsonNull; + + return clone; + } /** * Sets the strict mode configuration for the JSON parser with default true value @@ -106,6 +126,17 @@ public class JSONParserConfiguration extends ParserConfiguration { public boolean isOverwriteDuplicateKey() { return this.overwriteDuplicateKey; } + + /** + * The parser's behavior when meeting a null value in a java map, controls whether the parser should ignore + * that map entry or write a JSON entry with a null value. + * + * @return The javaNullAsJsonNull configuration value. + */ + public boolean isJavaNullAsJsonNull() { + return this.javaNullAsJsonNull; + } + /** * The parser throws an Exception when strict mode is true and tries to parse invalid JSON characters. diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 4c3413f..5762ea7 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -616,6 +616,46 @@ public class JSONObjectTest { assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey"))); Util.checkJSONObjectMaps(jsonObject); } + + @Test + public void jsonObjectByMapWithNullValueAndParserConfiguration() { + Map map = new HashMap(); + map.put("nullKey", null); + + // by default, null values are ignored + JSONObject obj1 = new JSONObject(map); + assertTrue("expected null value to be ignored by default", obj1.isEmpty()); + + // if configured, null values are written as such into the JSONObject. + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONObject obj2 = new JSONObject(map, parserConfiguration); + assertFalse("expected null value to accepted when configured", obj2.isEmpty()); + assertTrue(obj2.has("nullKey")); + assertEquals(JSONObject.NULL, obj2.get("nullKey")); + } + + @Test + public void jsonObjectByMapWithNestedNullValueAndParserConfiguration() { + Map map = new HashMap(); + Map nestedMap = new HashMap(); + nestedMap.put("nullKey", null); + map.put("nestedMap", nestedMap); + List> nestedList = new ArrayList>(); + nestedList.add(nestedMap); + map.put("nestedList", nestedList); + + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONObject jsonObject = new JSONObject(map, parserConfiguration); + + JSONObject nestedObject = jsonObject.getJSONObject("nestedMap"); + assertTrue(nestedObject.has("nullKey")); + assertEquals(JSONObject.NULL, nestedObject.get("nullKey")); + + JSONArray nestedArray = jsonObject.getJSONArray("nestedList"); + assertEquals(1, nestedArray.length()); + assertTrue(nestedArray.getJSONObject(0).has("nullKey")); + assertEquals(JSONObject.NULL, nestedArray.getJSONObject(0).get("nullKey")); + } /** * JSONObject built from a bean. In this case all but one of the From 5d1c789490627ddfa59f17252954ed62ccca6a59 Mon Sep 17 00:00:00 2001 From: Robert Lichtenberger Date: Wed, 19 Mar 2025 08:10:33 +0100 Subject: [PATCH 2/4] Add test for JSONArray from Java collection. --- src/test/java/org/json/junit/JSONArrayTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java index 580fe82..12ef3b9 100644 --- a/src/test/java/org/json/junit/JSONArrayTest.java +++ b/src/test/java/org/json/junit/JSONArrayTest.java @@ -228,6 +228,19 @@ public class JSONArrayTest { Util.checkJSONArrayMaps(jaRaw); Util.checkJSONArrayMaps(jaInt); } + + @Test + public void jsonArrayByListWithNestedNullValue() { + List> list = new ArrayList>(); + Map sub = new HashMap(); + sub.put("nullKey", null); + list.add(sub); + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONArray jsonArray = new JSONArray(list, parserConfiguration); + JSONObject subObject = jsonArray.getJSONObject(0); + assertTrue(subObject.has("nullKey")); + assertEquals(JSONObject.NULL, subObject.get("nullKey")); + } /** * Tests consecutive calls to putAll with array and collection. From 1afd7cd6bc5554ccaef541657866136196d38434 Mon Sep 17 00:00:00 2001 From: Robert Lichtenberger Date: Fri, 21 Mar 2025 07:25:37 +0100 Subject: [PATCH 3/4] Use better name for parser configuration option, fix API comment. --- src/main/java/org/json/JSONObject.java | 2 +- .../org/json/JSONParserConfiguration.java | 27 ++++++++++--------- .../java/org/json/junit/JSONArrayTest.java | 2 +- .../java/org/json/junit/JSONObjectTest.java | 4 +-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 73835d4..a1664f7 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -332,7 +332,7 @@ public class JSONObject { throw new NullPointerException("Null key."); } final Object value = e.getValue(); - if (value != null || jsonParserConfiguration.isJavaNullAsJsonNull()) { + if (value != null || jsonParserConfiguration.isUseNativeNulls()) { testValidity(value); this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration)); } diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index 58d080f..550eedf 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -10,9 +10,9 @@ public class JSONParserConfiguration extends ParserConfiguration { private boolean overwriteDuplicateKey; /** - * Used to indicate whether ignore null values when converting java maps to JSONObject or not. + * Used to indicate whether to convert java null values to JSONObject.NULL or ignoring the entry when converting java maps. */ - private boolean javaNullAsJsonNull; + private boolean useNativeNulls; /** * Configuration with the default values. @@ -74,16 +74,16 @@ public class JSONParserConfiguration extends ParserConfiguration { } /** - * Controls the parser's behavior when meeting duplicate keys. - * If set to false, the parser will throw a JSONException when meeting a duplicate key. - * Or the duplicate key's value will be overwritten. + * Controls the parser's behavior when meeting Java null values while converting maps. + * If set to true, the parser will put a JSONObject.NULL into the resulting JSONObject. + * Or the map entry will be ignored. * - * @param javaNullAsJsonNull define, if the parser should ignore null values in Java maps + * @param useNativeNulls defines if the parser should convert null values in Java maps * @return The existing configuration will not be modified. A new configuration is returned. */ - public JSONParserConfiguration withJavaNullAsJsonNull(final boolean javaNullAsJsonNull) { + public JSONParserConfiguration withUseNativeNulls(final boolean useNativeNulls) { JSONParserConfiguration clone = this.clone(); - clone.javaNullAsJsonNull = javaNullAsJsonNull; + clone.useNativeNulls = useNativeNulls; return clone; } @@ -128,13 +128,14 @@ public class JSONParserConfiguration extends ParserConfiguration { } /** - * The parser's behavior when meeting a null value in a java map, controls whether the parser should ignore - * that map entry or write a JSON entry with a null value. + * The parser's behavior when meeting a null value in a java map, controls whether the parser should + * write a JSON entry with a null value (isUseNativeNulls() == true) + * or ignore that map entry (isUseNativeNulls() == false). * - * @return The javaNullAsJsonNull configuration value. + * @return The useNativeNulls configuration value. */ - public boolean isJavaNullAsJsonNull() { - return this.javaNullAsJsonNull; + public boolean isUseNativeNulls() { + return this.useNativeNulls; } diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java index 12ef3b9..4296203 100644 --- a/src/test/java/org/json/junit/JSONArrayTest.java +++ b/src/test/java/org/json/junit/JSONArrayTest.java @@ -235,7 +235,7 @@ public class JSONArrayTest { Map sub = new HashMap(); sub.put("nullKey", null); list.add(sub); - JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true); JSONArray jsonArray = new JSONArray(list, parserConfiguration); JSONObject subObject = jsonArray.getJSONObject(0); assertTrue(subObject.has("nullKey")); diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 5762ea7..9d9ef8f 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -627,7 +627,7 @@ public class JSONObjectTest { assertTrue("expected null value to be ignored by default", obj1.isEmpty()); // if configured, null values are written as such into the JSONObject. - JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true); JSONObject obj2 = new JSONObject(map, parserConfiguration); assertFalse("expected null value to accepted when configured", obj2.isEmpty()); assertTrue(obj2.has("nullKey")); @@ -644,7 +644,7 @@ public class JSONObjectTest { nestedList.add(nestedMap); map.put("nestedList", nestedList); - JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withJavaNullAsJsonNull(true); + JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true); JSONObject jsonObject = new JSONObject(map, parserConfiguration); JSONObject nestedObject = jsonObject.getJSONObject("nestedMap"); From fd0cca358653dc970683bdc32e9ae5a928576806 Mon Sep 17 00:00:00 2001 From: Robert Lichtenberger Date: Fri, 21 Mar 2025 10:12:03 +0100 Subject: [PATCH 4/4] Fix cloning of parser configuration. --- src/main/java/org/json/JSONParserConfiguration.java | 1 + .../java/org/json/junit/JSONParserConfigurationTest.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index 550eedf..0cfa2ea 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -37,6 +37,7 @@ public class JSONParserConfiguration extends ParserConfiguration { clone.strictMode = strictMode; clone.maxNestingDepth = maxNestingDepth; clone.keepStrings = keepStrings; + clone.useNativeNulls = useNativeNulls; return clone; } diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java index 0ecf938..926c49f 100644 --- a/src/test/java/org/json/junit/JSONParserConfigurationTest.java +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -53,6 +53,14 @@ public class JSONParserConfigurationTest { assertTrue(jsonParserConfiguration.isKeepStrings()); } + + @Test + public void useNativeNullsIsCloned() { + JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() + .withUseNativeNulls(true) + .withStrictMode(true); + assertTrue(jsonParserConfiguration.isUseNativeNulls()); + } @Test public void verifyDuplicateKeyThenMaxDepth() {