From 10514e48cb665a973e8c9b04db577fa6fdaddf07 Mon Sep 17 00:00:00 2001 From: XIAYM-gh Date: Tue, 13 Feb 2024 18:56:10 +0800 Subject: [PATCH 01/18] Implemented custom duplicate key handling - Supports: throw an exception (by default), ignore, overwrite & merge into a JSONArray - With tests, 4/4 passed. --- .../org/json/JSONDuplicateKeyStrategy.java | 28 ++++++ src/main/java/org/json/JSONObject.java | 96 +++++++++++++++---- .../org/json/JSONParserConfiguration.java | 54 ++++++++--- .../junit/JSONObjectDuplicateKeyTest.java | 47 +++++++++ 4 files changed, 191 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/json/JSONDuplicateKeyStrategy.java create mode 100644 src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java diff --git a/src/main/java/org/json/JSONDuplicateKeyStrategy.java b/src/main/java/org/json/JSONDuplicateKeyStrategy.java new file mode 100644 index 0000000..4652dbc --- /dev/null +++ b/src/main/java/org/json/JSONDuplicateKeyStrategy.java @@ -0,0 +1,28 @@ +package org.json; + +/** + * An enum class that is supposed to be used in {@link JSONParserConfiguration}, + * it dedicates which way should be used to handle duplicate keys. + */ +public enum JSONDuplicateKeyStrategy { + /** + * The default value. And this is the way it used to be in the previous versions.
+ * The JSONParser will throw an {@link JSONException} when meet duplicate key. + */ + THROW_EXCEPTION, + + /** + * The JSONParser will ignore duplicate keys and won't overwrite the value of the key. + */ + IGNORE, + + /** + * The JSONParser will overwrite the old value of the key. + */ + OVERWRITE, + + /** + * The JSONParser will try to merge the values of the duplicate key into a {@link JSONArray}. + */ + MERGE_INTO_ARRAY +} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 039f136..e7d5cd5 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -15,17 +15,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; import java.util.regex.Pattern; import static org.json.NumberConversionUtil.potentialNumber; @@ -203,9 +194,28 @@ public class JSONObject { * duplicated key. */ public JSONObject(JSONTokener x) throws JSONException { + this(x, new JSONParserConfiguration()); + } + + /** + * Construct a JSONObject from a JSONTokener with custom json parse configurations. + * + * @param x + * A JSONTokener object containing the source string. + * @param jsonParserConfiguration + * Variable to pass parser custom configuration for json parsing. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { this(); char c; String key; + JSONDuplicateKeyStrategy duplicateKeyStrategy = jsonParserConfiguration.getDuplicateKeyStrategy(); + + // A list to store merged keys + List mergedKeys = null; if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); @@ -232,14 +242,45 @@ public class JSONObject { if (key != null) { // Check if key exists - if (this.opt(key) != null) { - // key already exists - throw x.syntaxError("Duplicate key \"" + key + "\""); + boolean keyExists = this.opt(key) != null; + // Read value early to make the tokener work well + Object value = null; + if (!keyExists || duplicateKeyStrategy != JSONDuplicateKeyStrategy.THROW_EXCEPTION) { + value = x.nextValue(); } - // Only add value if non-null - Object value = x.nextValue(); - if (value!=null) { - this.put(key, value); + + if (keyExists) { + switch (duplicateKeyStrategy) { + case THROW_EXCEPTION: + throw x.syntaxError("Duplicate key \"" + key + "\""); + + case MERGE_INTO_ARRAY: + if (mergedKeys == null) { + mergedKeys = new ArrayList<>(); + } + + Object current = this.get(key); + if (current instanceof JSONArray && mergedKeys.contains(key)) { + ((JSONArray) current).put(value); + break; + } + + JSONArray merged = new JSONArray(); + merged.put(current); + merged.put(value); + this.put(key, merged); + mergedKeys.add(key); + break; + } + + // == IGNORE, ignored :) + } + + if (!keyExists || duplicateKeyStrategy == JSONDuplicateKeyStrategy.OVERWRITE) { + // Only add value if non-null + if (value != null) { + this.put(key, value); + } } } @@ -294,7 +335,6 @@ public class JSONObject { /** * Construct a JSONObject from a map with recursion depth. - * */ private JSONObject(Map m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) { if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) { @@ -426,7 +466,25 @@ public class JSONObject { * duplicated key. */ public JSONObject(String source) throws JSONException { - this(new JSONTokener(source)); + this(source, new JSONParserConfiguration()); + } + + /** + * Construct a JSONObject from a source JSON text string with custom json parse configurations. + * This is the most commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace). + * @param jsonParserConfiguration + * Variable to pass parser custom configuration for json parsing. + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException { + this(new JSONTokener(source), jsonParserConfiguration); } /** diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index f95e244..f1ea2b2 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -4,23 +4,47 @@ package org.json; * Configuration object for the JSON parser. The configuration is immutable. */ public class JSONParserConfiguration extends ParserConfiguration { + /** + * The way should be used to handle duplicate keys. + */ + private JSONDuplicateKeyStrategy duplicateKeyStrategy; - /** - * Configuration with the default values. - */ - public JSONParserConfiguration() { - super(); - } + /** + * Configuration with the default values. + */ + public JSONParserConfiguration() { + this(JSONDuplicateKeyStrategy.THROW_EXCEPTION); + } - @Override - protected JSONParserConfiguration clone() { - return new JSONParserConfiguration(); - } + /** + * Configure the parser with {@link JSONDuplicateKeyStrategy}. + * + * @param duplicateKeyStrategy Indicate which way should be used to handle duplicate keys. + */ + public JSONParserConfiguration(JSONDuplicateKeyStrategy duplicateKeyStrategy) { + super(); + this.duplicateKeyStrategy = duplicateKeyStrategy; + } - @SuppressWarnings("unchecked") - @Override - public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) { - return super.withMaxNestingDepth(maxNestingDepth); - } + @Override + protected JSONParserConfiguration clone() { + return new JSONParserConfiguration(); + } + @SuppressWarnings("unchecked") + @Override + public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) { + return super.withMaxNestingDepth(maxNestingDepth); + } + + public JSONParserConfiguration withDuplicateKeyStrategy(final JSONDuplicateKeyStrategy duplicateKeyStrategy) { + JSONParserConfiguration newConfig = this.clone(); + newConfig.duplicateKeyStrategy = duplicateKeyStrategy; + + return newConfig; + } + + public JSONDuplicateKeyStrategy getDuplicateKeyStrategy() { + return this.duplicateKeyStrategy; + } } diff --git a/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java b/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java new file mode 100644 index 0000000..73dc70b --- /dev/null +++ b/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java @@ -0,0 +1,47 @@ +package org.json.junit; + +import org.json.*; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class JSONObjectDuplicateKeyTest { + private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\", \"key\": \"value3\"}"; + + @Test(expected = JSONException.class) + public void testThrowException() { + new JSONObject(TEST_SOURCE); + } + + @Test + public void testIgnore() { + JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( + JSONDuplicateKeyStrategy.IGNORE + )); + + assertEquals("duplicate key shouldn't be overwritten", "value1", jsonObject.getString("key")); + } + + @Test + public void testOverwrite() { + JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( + JSONDuplicateKeyStrategy.OVERWRITE + )); + + assertEquals("duplicate key should be overwritten", "value3", jsonObject.getString("key")); + } + + @Test + public void testMergeIntoArray() { + JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( + JSONDuplicateKeyStrategy.MERGE_INTO_ARRAY + )); + + JSONArray jsonArray; + assertTrue("duplicate key should be merged into JSONArray", jsonObject.get("key") instanceof JSONArray + && (jsonArray = jsonObject.getJSONArray("key")).length() == 3 + && jsonArray.getString(0).equals("value1") && jsonArray.getString(1).equals("value2") + && jsonArray.getString(2).equals("value3")); + } +} From 21a9fae7b042829f76e1f8ae7aab1ece66f72fb3 Mon Sep 17 00:00:00 2001 From: XIAYM-gh Date: Tue, 13 Feb 2024 22:33:30 +0800 Subject: [PATCH 02/18] Try making java 6 & old version javadoc generator compatible --- src/main/java/org/json/JSONDuplicateKeyStrategy.java | 2 +- src/main/java/org/json/JSONObject.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/json/JSONDuplicateKeyStrategy.java b/src/main/java/org/json/JSONDuplicateKeyStrategy.java index 4652dbc..954ac3a 100644 --- a/src/main/java/org/json/JSONDuplicateKeyStrategy.java +++ b/src/main/java/org/json/JSONDuplicateKeyStrategy.java @@ -6,7 +6,7 @@ package org.json; */ public enum JSONDuplicateKeyStrategy { /** - * The default value. And this is the way it used to be in the previous versions.
+ * The default value. And this is the way it used to be in the previous versions.
* The JSONParser will throw an {@link JSONException} when meet duplicate key. */ THROW_EXCEPTION, diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index e7d5cd5..1572a81 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -256,7 +256,7 @@ public class JSONObject { case MERGE_INTO_ARRAY: if (mergedKeys == null) { - mergedKeys = new ArrayList<>(); + mergedKeys = new ArrayList(); } Object current = this.get(key); From cb2c8d39629115e740fbd05d56669f44daa597e0 Mon Sep 17 00:00:00 2001 From: XIAYM-gh Date: Wed, 14 Feb 2024 17:53:58 +0800 Subject: [PATCH 03/18] Revert some unnecessary changes (mentioned in #840) --- .../org/json/JSONDuplicateKeyStrategy.java | 28 ----------- src/main/java/org/json/JSONObject.java | 46 +++---------------- .../org/json/JSONParserConfiguration.java | 24 +++++----- .../junit/JSONObjectDuplicateKeyTest.java | 30 ++---------- 4 files changed, 22 insertions(+), 106 deletions(-) delete mode 100644 src/main/java/org/json/JSONDuplicateKeyStrategy.java diff --git a/src/main/java/org/json/JSONDuplicateKeyStrategy.java b/src/main/java/org/json/JSONDuplicateKeyStrategy.java deleted file mode 100644 index 954ac3a..0000000 --- a/src/main/java/org/json/JSONDuplicateKeyStrategy.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.json; - -/** - * An enum class that is supposed to be used in {@link JSONParserConfiguration}, - * it dedicates which way should be used to handle duplicate keys. - */ -public enum JSONDuplicateKeyStrategy { - /** - * The default value. And this is the way it used to be in the previous versions.
- * The JSONParser will throw an {@link JSONException} when meet duplicate key. - */ - THROW_EXCEPTION, - - /** - * The JSONParser will ignore duplicate keys and won't overwrite the value of the key. - */ - IGNORE, - - /** - * The JSONParser will overwrite the old value of the key. - */ - OVERWRITE, - - /** - * The JSONParser will try to merge the values of the duplicate key into a {@link JSONArray}. - */ - MERGE_INTO_ARRAY -} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 1572a81..317fd3d 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -212,10 +212,6 @@ public class JSONObject { this(); char c; String key; - JSONDuplicateKeyStrategy duplicateKeyStrategy = jsonParserConfiguration.getDuplicateKeyStrategy(); - - // A list to store merged keys - List mergedKeys = null; if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); @@ -243,44 +239,14 @@ public class JSONObject { if (key != null) { // Check if key exists boolean keyExists = this.opt(key) != null; - // Read value early to make the tokener work well - Object value = null; - if (!keyExists || duplicateKeyStrategy != JSONDuplicateKeyStrategy.THROW_EXCEPTION) { - value = x.nextValue(); + if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) { + throw x.syntaxError("Duplicate key \"" + key + "\""); } - if (keyExists) { - switch (duplicateKeyStrategy) { - case THROW_EXCEPTION: - throw x.syntaxError("Duplicate key \"" + key + "\""); - - case MERGE_INTO_ARRAY: - if (mergedKeys == null) { - mergedKeys = new ArrayList(); - } - - Object current = this.get(key); - if (current instanceof JSONArray && mergedKeys.contains(key)) { - ((JSONArray) current).put(value); - break; - } - - JSONArray merged = new JSONArray(); - merged.put(current); - merged.put(value); - this.put(key, merged); - mergedKeys.add(key); - break; - } - - // == IGNORE, ignored :) - } - - if (!keyExists || duplicateKeyStrategy == JSONDuplicateKeyStrategy.OVERWRITE) { - // Only add value if non-null - if (value != null) { - this.put(key, value); - } + Object value = x.nextValue(); + // Only add value if non-null + if (value != null) { + this.put(key, value); } } diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index f1ea2b2..0d8706c 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -5,25 +5,27 @@ package org.json; */ public class JSONParserConfiguration extends ParserConfiguration { /** - * The way should be used to handle duplicate keys. + * Used to indicate whether to overwrite duplicate key or not. */ - private JSONDuplicateKeyStrategy duplicateKeyStrategy; + private boolean overwriteDuplicateKey; /** * Configuration with the default values. */ public JSONParserConfiguration() { - this(JSONDuplicateKeyStrategy.THROW_EXCEPTION); + this(false); } /** - * Configure the parser with {@link JSONDuplicateKeyStrategy}. + * Configure the parser with argument overwriteDuplicateKey. * - * @param duplicateKeyStrategy Indicate which way should be used to handle duplicate keys. + * @param overwriteDuplicateKey Indicate whether to overwrite duplicate key or not.
+ * If not, the JSONParser will throw a {@link JSONException} + * when meeting duplicate keys. */ - public JSONParserConfiguration(JSONDuplicateKeyStrategy duplicateKeyStrategy) { + public JSONParserConfiguration(boolean overwriteDuplicateKey) { super(); - this.duplicateKeyStrategy = duplicateKeyStrategy; + this.overwriteDuplicateKey = overwriteDuplicateKey; } @Override @@ -37,14 +39,14 @@ public class JSONParserConfiguration extends ParserConfiguration { return super.withMaxNestingDepth(maxNestingDepth); } - public JSONParserConfiguration withDuplicateKeyStrategy(final JSONDuplicateKeyStrategy duplicateKeyStrategy) { + public JSONParserConfiguration withOverwriteDuplicateKey(final boolean overwriteDuplicateKey) { JSONParserConfiguration newConfig = this.clone(); - newConfig.duplicateKeyStrategy = duplicateKeyStrategy; + newConfig.overwriteDuplicateKey = overwriteDuplicateKey; return newConfig; } - public JSONDuplicateKeyStrategy getDuplicateKeyStrategy() { - return this.duplicateKeyStrategy; + public boolean isOverwriteDuplicateKey() { + return this.overwriteDuplicateKey; } } diff --git a/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java b/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java index 73dc70b..1a3525b 100644 --- a/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java +++ b/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java @@ -7,41 +7,17 @@ import static org.junit.Assert.*; import org.junit.Test; public class JSONObjectDuplicateKeyTest { - private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\", \"key\": \"value3\"}"; + private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}"; @Test(expected = JSONException.class) public void testThrowException() { new JSONObject(TEST_SOURCE); } - @Test - public void testIgnore() { - JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( - JSONDuplicateKeyStrategy.IGNORE - )); - - assertEquals("duplicate key shouldn't be overwritten", "value1", jsonObject.getString("key")); - } - @Test public void testOverwrite() { - JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( - JSONDuplicateKeyStrategy.OVERWRITE - )); + JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration(true)); - assertEquals("duplicate key should be overwritten", "value3", jsonObject.getString("key")); - } - - @Test - public void testMergeIntoArray() { - JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration( - JSONDuplicateKeyStrategy.MERGE_INTO_ARRAY - )); - - JSONArray jsonArray; - assertTrue("duplicate key should be merged into JSONArray", jsonObject.get("key") instanceof JSONArray - && (jsonArray = jsonObject.getJSONArray("key")).length() == 3 - && jsonArray.getString(0).equals("value1") && jsonArray.getString(1).equals("value2") - && jsonArray.getString(2).equals("value3")); + assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); } } From 86253211c293b59a19e0d52eff42566bbd7d3d45 Mon Sep 17 00:00:00 2001 From: Valentyn Kolesnikov Date: Sun, 18 Feb 2024 04:20:33 +0200 Subject: [PATCH 04/18] Added missing Javadocs for Java 21 --- src/main/java/org/json/CDL.java | 6 ++++++ src/main/java/org/json/Cookie.java | 6 ++++++ src/main/java/org/json/CookieList.java | 6 ++++++ src/main/java/org/json/HTTP.java | 6 ++++++ src/main/java/org/json/JSONML.java | 7 +++++++ src/main/java/org/json/JSONPointer.java | 6 ++++++ src/main/java/org/json/JSONPropertyName.java | 1 + src/main/java/org/json/Property.java | 7 +++++++ src/main/java/org/json/XML.java | 6 ++++++ 9 files changed, 51 insertions(+) diff --git a/src/main/java/org/json/CDL.java b/src/main/java/org/json/CDL.java index 26dc2da..b495de1 100644 --- a/src/main/java/org/json/CDL.java +++ b/src/main/java/org/json/CDL.java @@ -25,6 +25,12 @@ Public Domain. */ public class CDL { + /** + * Constructs a new CDL object. + */ + public CDL() { + } + /** * Get the next value. The value can be wrapped in quotes. The value can * be empty. diff --git a/src/main/java/org/json/Cookie.java b/src/main/java/org/json/Cookie.java index 7a7e028..ab908a3 100644 --- a/src/main/java/org/json/Cookie.java +++ b/src/main/java/org/json/Cookie.java @@ -15,6 +15,12 @@ Public Domain. */ public class Cookie { + /** + * Constructs a new Cookie object. + */ + public Cookie() { + } + /** * Produce a copy of a string in which the characters '+', '%', '=', ';' * and control characters are replaced with "%hh". This is a gentle form diff --git a/src/main/java/org/json/CookieList.java b/src/main/java/org/json/CookieList.java index 03e54b9..d1064db 100644 --- a/src/main/java/org/json/CookieList.java +++ b/src/main/java/org/json/CookieList.java @@ -11,6 +11,12 @@ Public Domain. */ public class CookieList { + /** + * Constructs a new CookieList object. + */ + public CookieList() { + } + /** * Convert a cookie list into a JSONObject. A cookie list is a sequence * of name/value pairs. The names are separated from the values by '='. diff --git a/src/main/java/org/json/HTTP.java b/src/main/java/org/json/HTTP.java index 6fee6ba..44ab3a6 100644 --- a/src/main/java/org/json/HTTP.java +++ b/src/main/java/org/json/HTTP.java @@ -13,6 +13,12 @@ import java.util.Locale; */ public class HTTP { + /** + * Constructs a new HTTP object. + */ + public HTTP() { + } + /** Carriage return/line feed. */ public static final String CRLF = "\r\n"; diff --git a/src/main/java/org/json/JSONML.java b/src/main/java/org/json/JSONML.java index 4aea014..7b53e4d 100644 --- a/src/main/java/org/json/JSONML.java +++ b/src/main/java/org/json/JSONML.java @@ -13,6 +13,13 @@ Public Domain. * @version 2016-01-30 */ public class JSONML { + + /** + * Constructs a new JSONML object. + */ + public JSONML() { + } + /** * Parse XML values and store them in a JSONArray. * @param x The XMLTokener containing the source string. diff --git a/src/main/java/org/json/JSONPointer.java b/src/main/java/org/json/JSONPointer.java index 91bd137..859e1e6 100644 --- a/src/main/java/org/json/JSONPointer.java +++ b/src/main/java/org/json/JSONPointer.java @@ -42,6 +42,12 @@ public class JSONPointer { */ public static class Builder { + /** + * Constructs a new Builder object. + */ + public Builder() { + } + // Segments for the eventual JSONPointer string private final List refTokens = new ArrayList(); diff --git a/src/main/java/org/json/JSONPropertyName.java b/src/main/java/org/json/JSONPropertyName.java index 4391bb7..0e4123f 100644 --- a/src/main/java/org/json/JSONPropertyName.java +++ b/src/main/java/org/json/JSONPropertyName.java @@ -21,6 +21,7 @@ import java.lang.annotation.Target; @Target({METHOD}) public @interface JSONPropertyName { /** + * The value of the JSON property. * @return The name of the property as to be used in the JSON Object. */ String value(); diff --git a/src/main/java/org/json/Property.java b/src/main/java/org/json/Property.java index 83694c0..ba6c569 100644 --- a/src/main/java/org/json/Property.java +++ b/src/main/java/org/json/Property.java @@ -13,6 +13,13 @@ import java.util.Properties; * @version 2015-05-05 */ public class Property { + + /** + * Constructs a new Property object. + */ + public Property() { + } + /** * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. * @param properties java.util.Properties diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 301c8ba..484463b 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -24,6 +24,12 @@ import static org.json.NumberConversionUtil.stringToNumber; @SuppressWarnings("boxing") public class XML { + /** + * Constructs a new XML object. + */ + public XML() { + } + /** The Character '&'. */ public static final Character AMP = '&'; From b4b39bb441d903da0597e9b626ff18c046935309 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 18 Feb 2024 15:29:44 -0600 Subject: [PATCH 05/18] pipeline-updates: Java 11 intermittent test failures, try not running in parallel --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 63540cc..c0d2c1a 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - max-parallel: 2 + max-parallel: 1 matrix: # build against supported Java LTS versions: java: [ 8, 11, 17, 21 ] From f0289413d6138f6da1beefba412bd3a3a1b4f02d Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 18 Feb 2024 15:45:13 -0600 Subject: [PATCH 06/18] pipeline-updates: Java 11 intermittent fail - try increasing stack size --- .github/workflows/pipeline.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index c0d2c1a..46c1592 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -52,7 +52,12 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' - name: Compile Java ${{ matrix.java }} - run: mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + run: | + if [ "${{ matrix.java }}" = "11" ]; then + MAVEN_OPTS="-Xss4m" mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + else + mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + fi - name: Run Tests ${{ matrix.java }} run: | mvn test -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} From cd631d970e964f3e42ed0175f7bba4430334740b Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 18 Feb 2024 15:54:29 -0600 Subject: [PATCH 07/18] pipeline-updates: Java 11 intermittent fail - try an earlier release (there is no later release --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 46c1592..edd0057 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -41,7 +41,7 @@ jobs: max-parallel: 1 matrix: # build against supported Java LTS versions: - java: [ 8, 11, 17, 21 ] + java: [ 8, '11.0.21', 17, 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v3 From c1107fa987a986dd41a876e7d85c1e82c96e407f Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 18 Feb 2024 16:17:41 -0600 Subject: [PATCH 08/18] pipeline-updates: Java 11 intermittent fail - try separate build --- .github/workflows/pipeline.yml | 61 +++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index edd0057..7350f7a 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -34,14 +34,15 @@ jobs: with: name: Create java 1.6 JAR path: target/*.jar - build: + + build-11: runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 1 matrix: # build against supported Java LTS versions: - java: [ 8, '11.0.21', 17, 21 ] + java: [ 11 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v3 @@ -52,12 +53,56 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' - name: Compile Java ${{ matrix.java }} - run: | - if [ "${{ matrix.java }}" = "11" ]; then - MAVEN_OPTS="-Xss4m" mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true - else - mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true - fi + run: mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + - name: Run Tests ${{ matrix.java }} + run: | + mvn test -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Build Test Report ${{ matrix.java }} + if: ${{ always() }} + run: | + mvn surefire-report:report-only -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Upload Test Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Results ${{ matrix.java }} + path: target/surefire-reports/ + - name: Upload Test Report ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Report ${{ matrix.java }} + path: target/site/ + - name: Package Jar ${{ matrix.java }} + run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true + - name: Upload Package Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Package Jar ${{ matrix.java }} + path: target/*.jar + + + build-matrix: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 2 + matrix: + # build against supported Java LTS versions: + java: [ 8, 17, 21 ] + name: Java ${{ matrix.java }} + steps: + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + - name: Compile Java ${{ matrix.java }} + run: mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true - name: Run Tests ${{ matrix.java }} run: | mvn test -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} From af8cb376c2015ef10dfcd80d99c49c7af0e2808f Mon Sep 17 00:00:00 2001 From: XIAYM-gh Date: Mon, 19 Feb 2024 18:58:25 +0800 Subject: [PATCH 09/18] Add tests (+ fix bugs) & missing javadoc --- .../org/json/JSONParserConfiguration.java | 38 +++++++++++++--- .../java/org/json/ParserConfiguration.java | 32 ++++++------- .../junit/JSONObjectDuplicateKeyTest.java | 23 ---------- .../junit/JSONParserConfigurationTest.java | 45 +++++++++++++++++++ 4 files changed, 94 insertions(+), 44 deletions(-) delete mode 100644 src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java create mode 100644 src/test/java/org/json/junit/JSONParserConfigurationTest.java diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index 0d8706c..fc16f61 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -30,22 +30,50 @@ public class JSONParserConfiguration extends ParserConfiguration { @Override protected JSONParserConfiguration clone() { - return new JSONParserConfiguration(); + JSONParserConfiguration clone = new JSONParserConfiguration(overwriteDuplicateKey); + clone.maxNestingDepth = maxNestingDepth; + return clone; } + /** + * Defines the maximum nesting depth that the parser will descend before throwing an exception + * when parsing a map into JSONObject or parsing a {@link java.util.Collection} instance into + * JSONArray. The default max nesting depth is 512, which means the parser will throw a JsonException + * if the maximum depth is reached. + * + * @param maxNestingDepth the maximum nesting depth allowed to the JSON parser + * @return The existing configuration will not be modified. A new configuration is returned. + */ @SuppressWarnings("unchecked") @Override public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) { - return super.withMaxNestingDepth(maxNestingDepth); + JSONParserConfiguration clone = this.clone(); + clone.maxNestingDepth = maxNestingDepth; + + 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 overwriteDuplicateKey defines should the parser overwrite duplicate keys. + * @return The existing configuration will not be modified. A new configuration is returned. + */ public JSONParserConfiguration withOverwriteDuplicateKey(final boolean overwriteDuplicateKey) { - JSONParserConfiguration newConfig = this.clone(); - newConfig.overwriteDuplicateKey = overwriteDuplicateKey; + JSONParserConfiguration clone = this.clone(); + clone.overwriteDuplicateKey = overwriteDuplicateKey; - return newConfig; + return clone; } + /** + * The parser's behavior when meeting duplicate keys, controls whether the parser should + * overwrite duplicate keys or not. + * + * @return The overwriteDuplicateKey configuration value. + */ public boolean isOverwriteDuplicateKey() { return this.overwriteDuplicateKey; } diff --git a/src/main/java/org/json/ParserConfiguration.java b/src/main/java/org/json/ParserConfiguration.java index 5cdc10d..06cc443 100644 --- a/src/main/java/org/json/ParserConfiguration.java +++ b/src/main/java/org/json/ParserConfiguration.java @@ -20,12 +20,12 @@ public class ParserConfiguration { /** * Specifies if values should be kept as strings (true), or if - * they should try to be guessed into JSON values (numeric, boolean, string) + * they should try to be guessed into JSON values (numeric, boolean, string). */ protected boolean keepStrings; /** - * The maximum nesting depth when parsing a document. + * The maximum nesting depth when parsing an object. */ protected int maxNestingDepth; @@ -59,14 +59,14 @@ public class ParserConfiguration { // map should be cloned as well. If the values of the map are known to also // be immutable, then a shallow clone of the map is acceptable. return new ParserConfiguration( - this.keepStrings, - this.maxNestingDepth + this.keepStrings, + this.maxNestingDepth ); } /** * When parsing the XML into JSONML, specifies if values should be kept as strings (true), or if - * they should try to be guessed into JSON values (numeric, boolean, string) + * they should try to be guessed into JSON values (numeric, boolean, string). * * @return The keepStrings configuration value. */ @@ -78,22 +78,21 @@ public class ParserConfiguration { * When parsing the XML into JSONML, specifies if values should be kept as strings (true), or if * they should try to be guessed into JSON values (numeric, boolean, string) * - * @param newVal - * new value to use for the keepStrings configuration option. - * @param the type of the configuration object - * + * @param newVal new value to use for the keepStrings configuration option. + * @param the type of the configuration object * @return The existing configuration will not be modified. A new configuration is returned. */ @SuppressWarnings("unchecked") public T withKeepStrings(final boolean newVal) { - T newConfig = (T)this.clone(); + T newConfig = (T) this.clone(); newConfig.keepStrings = newVal; return newConfig; } /** * The maximum nesting depth that the parser will descend before throwing an exception - * when parsing the XML into JSONML. + * when parsing an object (e.g. Map, Collection) into JSON-related objects. + * * @return the maximum nesting depth set for this configuration */ public int getMaxNestingDepth() { @@ -102,18 +101,19 @@ public class ParserConfiguration { /** * Defines the maximum nesting depth that the parser will descend before throwing an exception - * when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser - * will throw a JsonException if the maximum depth is reached. + * when parsing an object (e.g. Map, Collection) into JSON-related objects. + * The default max nesting depth is 512, which means the parser will throw a JsonException if + * the maximum depth is reached. * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth, * which means the parses will go as deep as the maximum call stack size allows. + * * @param maxNestingDepth the maximum nesting depth allowed to the XML parser - * @param the type of the configuration object - * + * @param the type of the configuration object * @return The existing configuration will not be modified. A new configuration is returned. */ @SuppressWarnings("unchecked") public T withMaxNestingDepth(int maxNestingDepth) { - T newConfig = (T)this.clone(); + T newConfig = (T) this.clone(); if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) { newConfig.maxNestingDepth = maxNestingDepth; diff --git a/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java b/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java deleted file mode 100644 index 1a3525b..0000000 --- a/src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.json.junit; - -import org.json.*; - -import static org.junit.Assert.*; - -import org.junit.Test; - -public class JSONObjectDuplicateKeyTest { - private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}"; - - @Test(expected = JSONException.class) - public void testThrowException() { - new JSONObject(TEST_SOURCE); - } - - @Test - public void testOverwrite() { - JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration(true)); - - assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); - } -} diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java new file mode 100644 index 0000000..0e80d77 --- /dev/null +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -0,0 +1,45 @@ +package org.json.junit; + +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONParserConfiguration; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JSONParserConfigurationTest { + private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}"; + + @Test(expected = JSONException.class) + public void testThrowException() { + new JSONObject(TEST_SOURCE); + } + + @Test + public void testOverwrite() { + JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration(true)); + + assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); + } + + @Test + public void verifyDuplicateKeyThenMaxDepth() { + JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() + .withOverwriteDuplicateKey(true) + .withMaxNestingDepth(42); + + assertEquals(42, jsonParserConfiguration.getMaxNestingDepth()); + assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey()); + } + + @Test + public void verifyMaxDepthThenDuplicateKey() { + JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() + .withMaxNestingDepth(42) + .withOverwriteDuplicateKey(true); + + assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey()); + assertEquals(42, jsonParserConfiguration.getMaxNestingDepth()); + } +} From 771c82c4eb2feed9584273ffcab8a37d1f903569 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 24 Feb 2024 13:07:51 -0600 Subject: [PATCH 10/18] backing out recent changes to optLong, getLong. See #868 --- src/main/java/org/json/JSONArray.java | 4 +- src/main/java/org/json/JSONObject.java | 95 +++++++++- .../java/org/json/NumberConversionUtil.java | 152 --------------- src/main/java/org/json/XML.java | 83 +++++++-- .../org/json/NumberConversionUtilTest.java | 174 ------------------ src/test/java/org/json/junit/JSONMLTest.java | 2 +- .../org/json/junit/JSONObjectDecimalTest.java | 100 ---------- .../org/json/junit/JSONObjectNumberTest.java | 6 +- .../java/org/json/junit/JSONObjectTest.java | 80 +------- .../org/json/junit/JsonNumberZeroTest.java | 55 ------ .../org/json/junit/XMLConfigurationTest.java | 2 +- src/test/java/org/json/junit/XMLTest.java | 2 +- 12 files changed, 175 insertions(+), 580 deletions(-) delete mode 100644 src/main/java/org/json/NumberConversionUtil.java delete mode 100644 src/test/java/org/json/NumberConversionUtilTest.java delete mode 100644 src/test/java/org/json/junit/JSONObjectDecimalTest.java delete mode 100644 src/test/java/org/json/junit/JsonNumberZeroTest.java diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index 38b0b31..f86075e 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -360,7 +360,7 @@ public class JSONArray implements Iterable { if (object instanceof Number) { return (Number)object; } - return NumberConversionUtil.stringToNumber(object.toString()); + return JSONObject.stringToNumber(object.toString()); } catch (Exception e) { throw wrongValueFormatException(index, "number", object, e); } @@ -1107,7 +1107,7 @@ public class JSONArray implements Iterable { if (val instanceof String) { try { - return NumberConversionUtil.stringToNumber((String) val); + return JSONObject.stringToNumber((String) val); } catch (Exception e) { return defaultValue; } diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 4c08b0b..6494f93 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -28,9 +28,6 @@ import java.util.ResourceBundle; import java.util.Set; import java.util.regex.Pattern; -import static org.json.NumberConversionUtil.potentialNumber; -import static org.json.NumberConversionUtil.stringToNumber; - /** * A JSONObject is an unordered collection of name/value pairs. Its external * form is a string wrapped in curly braces with colons between the names and @@ -2460,7 +2457,8 @@ public class JSONObject { * produced, then the value will just be a string. */ - if (potentialNumber(string)) { + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { try { return stringToNumber(string); } catch (Exception ignore) { @@ -2469,8 +2467,75 @@ public class JSONObject { return string; } + /** + * Converts a string to a number using the narrowest possible type. Possible + * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. + * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. + * + * @param val value to convert + * @return Number representation of the value. + * @throws NumberFormatException thrown if the value is not a valid number. A public + * caller should catch this and wrap it in a {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (isDecimalNotation(val)) { + // Use a BigDecimal all the time so we keep the original + // representation. BigDecimal doesn't support -0.0, ensure we + // keep that by forcing a decimal. + try { + BigDecimal bd = new BigDecimal(val); + if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { + return Double.valueOf(-0.0); + } + return bd; + } catch (NumberFormatException retryAsDouble) { + // this is to support "Hex Floats" like this: 0x1.0P-1074 + try { + Double d = Double.valueOf(val); + if(d.isNaN() || d.isInfinite()) { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + return d; + } catch (NumberFormatException ignore) { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } + } + // block items like 00 01 etc. Java number parsers treat these as Octal. + if(initial == '0' && val.length() > 1) { + char at1 = val.charAt(1); + if(at1 >= '0' && at1 <= '9') { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } else if (initial == '-' && val.length() > 2) { + char at1 = val.charAt(1); + char at2 = val.charAt(2); + if(at1 == '0' && at2 >= '0' && at2 <= '9') { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) - + // BigInteger down conversion: We use a similar bitLength compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. + BigInteger bi = new BigInteger(val); + if(bi.bitLength() <= 31){ + return Integer.valueOf(bi.intValue()); + } + if(bi.bitLength() <= 63){ + return Long.valueOf(bi.longValue()); + } + return bi; + } + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } /** * Throw an exception if the object is a NaN or infinite number. @@ -2896,5 +2961,23 @@ public class JSONObject { ); } - + /** + * For a prospective number, remove the leading zeros + * @param value prospective number + * @return number without leading zeros + */ + private static String removeLeadingZerosOfNumber(String value){ + if (value.equals("-")){return value;} + boolean negativeFirstChar = (value.charAt(0) == '-'); + int counter = negativeFirstChar ? 1:0; + while (counter < value.length()){ + if (value.charAt(counter) != '0'){ + if (negativeFirstChar) {return "-".concat(value.substring(counter));} + return value.substring(counter); + } + ++counter; + } + if (negativeFirstChar) {return "-0";} + return "0"; + } } diff --git a/src/main/java/org/json/NumberConversionUtil.java b/src/main/java/org/json/NumberConversionUtil.java deleted file mode 100644 index c2f16d7..0000000 --- a/src/main/java/org/json/NumberConversionUtil.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.json; - -import java.math.BigDecimal; -import java.math.BigInteger; - -class NumberConversionUtil { - - /** - * Converts a string to a number using the narrowest possible type. Possible - * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. - * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. - * - * @param input value to convert - * @return Number representation of the value. - * @throws NumberFormatException thrown if the value is not a valid number. A public - * caller should catch this and wrap it in a {@link JSONException} if applicable. - */ - static Number stringToNumber(final String input) throws NumberFormatException { - String val = input; - if (val.startsWith(".")){ - val = "0"+val; - } - if (val.startsWith("-.")){ - val = "-0."+val.substring(2); - } - char initial = val.charAt(0); - if ( isNumericChar(initial) || initial == '-' ) { - // decimal representation - if (isDecimalNotation(val)) { - // Use a BigDecimal all the time so we keep the original - // representation. BigDecimal doesn't support -0.0, ensure we - // keep that by forcing a decimal. - try { - BigDecimal bd = new BigDecimal(val); - if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { - return Double.valueOf(-0.0); - } - return bd; - } catch (NumberFormatException retryAsDouble) { - // this is to support "Hex Floats" like this: 0x1.0P-1074 - try { - Double d = Double.valueOf(val); - if(d.isNaN() || d.isInfinite()) { - throw new NumberFormatException("val ["+input+"] is not a valid number."); - } - return d; - } catch (NumberFormatException ignore) { - throw new NumberFormatException("val ["+input+"] is not a valid number."); - } - } - } - val = removeLeadingZerosOfNumber(input); - initial = val.charAt(0); - if(initial == '0' && val.length() > 1) { - char at1 = val.charAt(1); - if(isNumericChar(at1)) { - throw new NumberFormatException("val ["+input+"] is not a valid number."); - } - } else if (initial == '-' && val.length() > 2) { - char at1 = val.charAt(1); - char at2 = val.charAt(2); - if(at1 == '0' && isNumericChar(at2)) { - throw new NumberFormatException("val ["+input+"] is not a valid number."); - } - } - // integer representation. - // This will narrow any values to the smallest reasonable Object representation - // (Integer, Long, or BigInteger) - - // BigInteger down conversion: We use a similar bitLength compare as - // BigInteger#intValueExact uses. Increases GC, but objects hold - // only what they need. i.e. Less runtime overhead if the value is - // long lived. - BigInteger bi = new BigInteger(val); - if(bi.bitLength() <= 31){ - return Integer.valueOf(bi.intValue()); - } - if(bi.bitLength() <= 63){ - return Long.valueOf(bi.longValue()); - } - return bi; - } - throw new NumberFormatException("val ["+input+"] is not a valid number."); - } - - /** - * Checks if the character is a numeric digit ('0' to '9'). - * - * @param c The character to be checked. - * @return true if the character is a numeric digit, false otherwise. - */ - private static boolean isNumericChar(char c) { - return (c <= '9' && c >= '0'); - } - - /** - * Checks if the value could be considered a number in decimal number system. - * @param value - * @return - */ - static boolean potentialNumber(String value){ - if (value == null || value.isEmpty()){ - return false; - } - return potentialPositiveNumberStartingAtIndex(value, (value.charAt(0)=='-'?1:0)); - } - - /** - * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. - * - * @param val value to test - * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise. - */ - private static boolean isDecimalNotation(final String val) { - return val.indexOf('.') > -1 || val.indexOf('e') > -1 - || val.indexOf('E') > -1 || "-0".equals(val); - } - - private static boolean potentialPositiveNumberStartingAtIndex(String value,int index){ - if (index >= value.length()){ - return false; - } - return digitAtIndex(value, (value.charAt(index)=='.'?index+1:index)); - } - - private static boolean digitAtIndex(String value, int index){ - if (index >= value.length()){ - return false; - } - return value.charAt(index) >= '0' && value.charAt(index) <= '9'; - } - - /** - * For a prospective number, remove the leading zeros - * @param value prospective number - * @return number without leading zeros - */ - private static String removeLeadingZerosOfNumber(String value){ - if (value.equals("-")){return value;} - boolean negativeFirstChar = (value.charAt(0) == '-'); - int counter = negativeFirstChar ? 1:0; - while (counter < value.length()){ - if (value.charAt(counter) != '0'){ - if (negativeFirstChar) {return "-".concat(value.substring(counter));} - return value.substring(counter); - } - ++counter; - } - if (negativeFirstChar) {return "-0";} - return "0"; - } -} diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 484463b..e59ec7a 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -10,10 +10,6 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.Iterator; -import static org.json.NumberConversionUtil.potentialNumber; -import static org.json.NumberConversionUtil.stringToNumber; - - /** * This provides static methods to convert an XML text into a JSONObject, and to * covert a JSONObject into an XML text. @@ -499,6 +495,76 @@ public class XML { return true; } + /** + * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support. + */ + private static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (isDecimalNotation(val)) { + // Use a BigDecimal all the time so we keep the original + // representation. BigDecimal doesn't support -0.0, ensure we + // keep that by forcing a decimal. + try { + BigDecimal bd = new BigDecimal(val); + if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { + return Double.valueOf(-0.0); + } + return bd; + } catch (NumberFormatException retryAsDouble) { + // this is to support "Hex Floats" like this: 0x1.0P-1074 + try { + Double d = Double.valueOf(val); + if(d.isNaN() || d.isInfinite()) { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + return d; + } catch (NumberFormatException ignore) { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } + } + // block items like 00 01 etc. Java number parsers treat these as Octal. + if(initial == '0' && val.length() > 1) { + char at1 = val.charAt(1); + if(at1 >= '0' && at1 <= '9') { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } else if (initial == '-' && val.length() > 2) { + char at1 = val.charAt(1); + char at2 = val.charAt(2); + if(at1 == '0' && at2 >= '0' && at2 <= '9') { + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + + // BigInteger down conversion: We use a similar bitLength compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. + BigInteger bi = new BigInteger(val); + if(bi.bitLength() <= 31){ + return Integer.valueOf(bi.intValue()); + } + if(bi.bitLength() <= 63){ + return Long.valueOf(bi.longValue()); + } + return bi; + } + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + + /** + * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support. + */ + private static boolean isDecimalNotation(final String val) { + return val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 || "-0".equals(val); + } /** * This method tries to convert the given string value to the target object @@ -543,7 +609,8 @@ public class XML { * produced, then the value will just be a string. */ - if (potentialNumber(string)) { + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { try { return stringToNumber(string); } catch (Exception ignore) { @@ -552,11 +619,6 @@ public class XML { return string; } - - - - - /** * Convert a well-formed (but not necessarily valid) XML string into a * JSONObject. Some information may be lost in this transformation because @@ -975,5 +1037,4 @@ public class XML { } return sb.toString(); } - } diff --git a/src/test/java/org/json/NumberConversionUtilTest.java b/src/test/java/org/json/NumberConversionUtilTest.java deleted file mode 100644 index c6f0725..0000000 --- a/src/test/java/org/json/NumberConversionUtilTest.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.json; - -import org.junit.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import static org.junit.Assert.*; - -public class NumberConversionUtilTest { - - @Test - public void shouldParseDecimalFractionNumbersWithMultipleLeadingZeros(){ - Number number = NumberConversionUtil.stringToNumber("00.10d"); - assertEquals("Do not match", 0.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", 0.10f, number.floatValue(),0.0f); - assertEquals("Do not match", 0, number.longValue(),0); - assertEquals("Do not match", 0, number.intValue(),0); - } - - @Test - public void shouldParseDecimalFractionNumbersWithSingleLeadingZero(){ - Number number = NumberConversionUtil.stringToNumber("0.10d"); - assertEquals("Do not match", 0.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", 0.10f, number.floatValue(),0.0f); - assertEquals("Do not match", 0, number.longValue(),0); - assertEquals("Do not match", 0, number.intValue(),0); - } - - - @Test - public void shouldParseDecimalFractionNumbersWithZerosAfterDecimalPoint(){ - Number number = NumberConversionUtil.stringToNumber("0.010d"); - assertEquals("Do not match", 0.010d, number.doubleValue(),0.0d); - assertEquals("Do not match", 0.010f, number.floatValue(),0.0f); - assertEquals("Do not match", 0, number.longValue(),0); - assertEquals("Do not match", 0, number.intValue(),0); - } - - @Test - public void shouldParseMixedDecimalFractionNumbersWithMultipleLeadingZeros(){ - Number number = NumberConversionUtil.stringToNumber("00200.10d"); - assertEquals("Do not match", 200.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", 200.10f, number.floatValue(),0.0f); - assertEquals("Do not match", 200, number.longValue(),0); - assertEquals("Do not match", 200, number.intValue(),0); - } - - @Test - public void shouldParseMixedDecimalFractionNumbersWithoutLeadingZero(){ - Number number = NumberConversionUtil.stringToNumber("200.10d"); - assertEquals("Do not match", 200.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", 200.10f, number.floatValue(),0.0f); - assertEquals("Do not match", 200, number.longValue(),0); - assertEquals("Do not match", 200, number.intValue(),0); - } - - - @Test - public void shouldParseMixedDecimalFractionNumbersWithZerosAfterDecimalPoint(){ - Number number = NumberConversionUtil.stringToNumber("200.010d"); - assertEquals("Do not match", 200.010d, number.doubleValue(),0.0d); - assertEquals("Do not match", 200.010f, number.floatValue(),0.0f); - assertEquals("Do not match", 200, number.longValue(),0); - assertEquals("Do not match", 200, number.intValue(),0); - } - - - @Test - public void shouldParseNegativeDecimalFractionNumbersWithMultipleLeadingZeros(){ - Number number = NumberConversionUtil.stringToNumber("-00.10d"); - assertEquals("Do not match", -0.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", -0.10f, number.floatValue(),0.0f); - assertEquals("Do not match", -0, number.longValue(),0); - assertEquals("Do not match", -0, number.intValue(),0); - } - - @Test - public void shouldParseNegativeDecimalFractionNumbersWithSingleLeadingZero(){ - Number number = NumberConversionUtil.stringToNumber("-0.10d"); - assertEquals("Do not match", -0.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", -0.10f, number.floatValue(),0.0f); - assertEquals("Do not match", -0, number.longValue(),0); - assertEquals("Do not match", -0, number.intValue(),0); - } - - - @Test - public void shouldParseNegativeDecimalFractionNumbersWithZerosAfterDecimalPoint(){ - Number number = NumberConversionUtil.stringToNumber("-0.010d"); - assertEquals("Do not match", -0.010d, number.doubleValue(),0.0d); - assertEquals("Do not match", -0.010f, number.floatValue(),0.0f); - assertEquals("Do not match", -0, number.longValue(),0); - assertEquals("Do not match", -0, number.intValue(),0); - } - - @Test - public void shouldParseNegativeMixedDecimalFractionNumbersWithMultipleLeadingZeros(){ - Number number = NumberConversionUtil.stringToNumber("-00200.10d"); - assertEquals("Do not match", -200.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", -200.10f, number.floatValue(),0.0f); - assertEquals("Do not match", -200, number.longValue(),0); - assertEquals("Do not match", -200, number.intValue(),0); - } - - @Test - public void shouldParseNegativeMixedDecimalFractionNumbersWithoutLeadingZero(){ - Number number = NumberConversionUtil.stringToNumber("-200.10d"); - assertEquals("Do not match", -200.10d, number.doubleValue(),0.0d); - assertEquals("Do not match", -200.10f, number.floatValue(),0.0f); - assertEquals("Do not match", -200, number.longValue(),0); - assertEquals("Do not match", -200, number.intValue(),0); - } - - - @Test - public void shouldParseNegativeMixedDecimalFractionNumbersWithZerosAfterDecimalPoint(){ - Number number = NumberConversionUtil.stringToNumber("-200.010d"); - assertEquals("Do not match", -200.010d, number.doubleValue(),0.0d); - assertEquals("Do not match", -200.010f, number.floatValue(),0.0f); - assertEquals("Do not match", -200, number.longValue(),0); - assertEquals("Do not match", -200, number.intValue(),0); - } - - @Test - public void shouldParseNumbersWithExponents(){ - Number number = NumberConversionUtil.stringToNumber("23.45e7"); - assertEquals("Do not match", 23.45e7d, number.doubleValue(),0.0d); - assertEquals("Do not match", 23.45e7f, number.floatValue(),0.0f); - assertEquals("Do not match", 2.345E8, number.longValue(),0); - assertEquals("Do not match", 2.345E8, number.intValue(),0); - } - - - @Test - public void shouldParseNegativeNumbersWithExponents(){ - Number number = NumberConversionUtil.stringToNumber("-23.45e7"); - assertEquals("Do not match", -23.45e7d, number.doubleValue(),0.0d); - assertEquals("Do not match", -23.45e7f, number.floatValue(),0.0f); - assertEquals("Do not match", -2.345E8, number.longValue(),0); - assertEquals("Do not match", -2.345E8, number.intValue(),0); - } - - @Test - public void shouldParseBigDecimal(){ - Number number = NumberConversionUtil.stringToNumber("19007199254740993.35481234487103587486413587843213584"); - assertTrue(number instanceof BigDecimal); - } - - @Test - public void shouldParseBigInteger(){ - Number number = NumberConversionUtil.stringToNumber("1900719925474099335481234487103587486413587843213584"); - assertTrue(number instanceof BigInteger); - } - - @Test - public void shouldIdentifyPotentialNumber(){ - assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("112.123")); - assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("112e123")); - assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("-112.123")); - assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("-112e23")); - assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("--112.123")); - assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("-a112.123")); - assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("a112.123")); - assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("e112.123")); - } - - @Test(expected = NumberFormatException.class) - public void shouldExpectExceptionWhenNumberIsNotFormatted(){ - NumberConversionUtil.stringToNumber("112.aa123"); - } - - -} \ No newline at end of file diff --git a/src/test/java/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java index e6abd15..154af64 100644 --- a/src/test/java/org/json/junit/JSONMLTest.java +++ b/src/test/java/org/json/junit/JSONMLTest.java @@ -709,7 +709,7 @@ public class JSONMLTest { @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; - final String expectedJsonString = "[\"root\",[\"id\",1],[\"id\",1],[\"id\",0],[\"id\",0],[\"item\",{\"id\":1}],[\"title\",true]]"; + final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",1],[\"id\",\"00\"],[\"id\",0],[\"item\",{\"id\":\"01\"}],[\"title\",true]]"; final JSONArray actualJsonOutput = JSONML.toJSONArray(originalXml, false); assertEquals(expectedJsonString, actualJsonOutput.toString()); } diff --git a/src/test/java/org/json/junit/JSONObjectDecimalTest.java b/src/test/java/org/json/junit/JSONObjectDecimalTest.java deleted file mode 100644 index 3302f0a..0000000 --- a/src/test/java/org/json/junit/JSONObjectDecimalTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.json.junit; - -import org.json.JSONObject; -import org.junit.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import static org.junit.Assert.assertEquals; - -public class JSONObjectDecimalTest { - - @Test - public void shouldParseDecimalNumberThatStartsWithDecimalPoint(){ - JSONObject jsonObject = new JSONObject("{value:0.50}"); - assertEquals("Float not recognized", 0.5f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", 0.5f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", 0.5f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", 0.5d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", 0.5d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", 0.5d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(.5).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - - - - @Test - public void shouldParseNegativeDecimalNumberThatStartsWithDecimalPoint(){ - JSONObject jsonObject = new JSONObject("{value:-.50}"); - assertEquals("Float not recognized", -0.5f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", -0.5f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", -0.5f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", -0.5d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", -0.5d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", -0.5d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(-.5).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - - @Test - public void shouldParseDecimalNumberThatHasZeroBeforeWithDecimalPoint(){ - JSONObject jsonObject = new JSONObject("{value:00.050}"); - assertEquals("Float not recognized", 0.05f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", 0.05f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", 0.05f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", 0.05d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", 0.05d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", 0.05d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(.05).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - - @Test - public void shouldParseNegativeDecimalNumberThatHasZeroBeforeWithDecimalPoint(){ - JSONObject jsonObject = new JSONObject("{value:-00.050}"); - assertEquals("Float not recognized", -0.05f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", -0.05f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", -0.05f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", -0.05d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", -0.05d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", -0.05d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(-.05).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - - -} diff --git a/src/test/java/org/json/junit/JSONObjectNumberTest.java b/src/test/java/org/json/junit/JSONObjectNumberTest.java index 14e68d6..43173a2 100644 --- a/src/test/java/org/json/junit/JSONObjectNumberTest.java +++ b/src/test/java/org/json/junit/JSONObjectNumberTest.java @@ -23,10 +23,7 @@ public class JSONObjectNumberTest { @Parameters(name = "{index}: {0}") public static Collection data() { return Arrays.asList(new Object[][]{ - {"{value:0050}", 1}, - {"{value:0050.0000}", 1}, - {"{value:-0050}", -1}, - {"{value:-0050.0000}", -1}, + {"{value:50}", 1}, {"{value:50.0}", 1}, {"{value:5e1}", 1}, {"{value:5E1}", 1}, @@ -35,7 +32,6 @@ public class JSONObjectNumberTest { {"{value:-50}", -1}, {"{value:-50.0}", -1}, {"{value:-5e1}", -1}, - {"{value:-0005e1}", -1}, {"{value:-5E1}", -1}, {"{value:-5e1}", -1}, {"{value:'-50'}", -1} diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 053f17a..d90297d 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -4,7 +4,6 @@ package org.json.junit; Public Domain. */ -import static java.lang.Double.NaN; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -786,7 +785,7 @@ public class JSONObjectTest { jsonObject.accumulate("myArray", -23.45e7); // include an unsupported object for coverage try { - jsonObject.accumulate("myArray", NaN); + jsonObject.accumulate("myArray", Double.NaN); fail("Expected exception"); } catch (JSONException ignored) {} @@ -818,7 +817,7 @@ public class JSONObjectTest { jsonObject.append("myArray", -23.45e7); // include an unsupported object for coverage try { - jsonObject.append("myArray", NaN); + jsonObject.append("myArray", Double.NaN); fail("Expected exception"); } catch (JSONException ignored) {} @@ -843,7 +842,7 @@ public class JSONObjectTest { public void jsonObjectDoubleToString() { String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" }; Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67, - NaN, Double.NEGATIVE_INFINITY }; + Double.NaN, Double.NEGATIVE_INFINITY }; for (int i = 0; i < expectedStrs.length; ++i) { String actualStr = JSONObject.doubleToString(doubles[i]); assertTrue("value expected ["+expectedStrs[i]+ @@ -898,11 +897,11 @@ public class JSONObjectTest { assertTrue("opt doubleKey should be double", jsonObject.optDouble("doubleKey") == -23.45e7); assertTrue("opt doubleKey with Default should be double", - jsonObject.optDouble("doubleStrKey", NaN) == 1); + jsonObject.optDouble("doubleStrKey", Double.NaN) == 1); assertTrue("opt doubleKey should be Double", Double.valueOf(-23.45e7).equals(jsonObject.optDoubleObject("doubleKey"))); assertTrue("opt doubleKey with Default should be Double", - Double.valueOf(1).equals(jsonObject.optDoubleObject("doubleStrKey", NaN))); + Double.valueOf(1).equals(jsonObject.optDoubleObject("doubleStrKey", Double.NaN))); assertTrue("opt negZeroKey should be a Double", jsonObject.opt("negZeroKey") instanceof Double); assertTrue("get negZeroKey should be a Double", @@ -1068,21 +1067,12 @@ public class JSONObjectTest { "\"tooManyZeros\":00,"+ "\"negativeInfinite\":-Infinity,"+ "\"negativeNaN\":-NaN,"+ - "\"negativeNaNWithLeadingZeros\":-00NaN,"+ "\"negativeFraction\":-.01,"+ "\"tooManyZerosFraction\":00.001,"+ "\"negativeHexFloat\":-0x1.fffp1,"+ "\"hexFloat\":0x1.0P-1074,"+ "\"floatIdentifier\":0.1f,"+ - "\"doubleIdentifier\":0.1d,"+ - "\"doubleIdentifierWithMultipleLeadingZerosBeforeDecimal\":0000000.1d,"+ - "\"negativeDoubleIdentifierWithMultipleLeadingZerosBeforeDecimal\":-0000000.1d,"+ - "\"doubleIdentifierWithMultipleLeadingZerosAfterDecimal\":0000000.0001d,"+ - "\"negativeDoubleIdentifierWithMultipleLeadingZerosAfterDecimal\":-0000000.0001d,"+ - "\"integerWithLeadingZeros\":000900,"+ - "\"integerWithAllZeros\":00000,"+ - "\"compositeWithLeadingZeros\":00800.90d,"+ - "\"decimalPositiveWithoutNumberBeforeDecimalPoint\":.90,"+ + "\"doubleIdentifier\":0.1d"+ "}"; JSONObject jsonObject = new JSONObject(str); Object obj; @@ -1092,22 +1082,15 @@ public class JSONObjectTest { assertTrue("hexNumber currently evaluates to string", obj.equals("-0x123")); assertTrue( "tooManyZeros currently evaluates to string", - jsonObject.get( "tooManyZeros" ).equals(0)); + jsonObject.get( "tooManyZeros" ).equals("00")); obj = jsonObject.get("negativeInfinite"); assertTrue( "negativeInfinite currently evaluates to string", obj.equals("-Infinity")); obj = jsonObject.get("negativeNaN"); assertTrue( "negativeNaN currently evaluates to string", obj.equals("-NaN")); - obj = jsonObject.get("negativeNaNWithLeadingZeros"); - assertTrue( "negativeNaNWithLeadingZeros currently evaluates to string", - obj.equals("-00NaN")); assertTrue( "negativeFraction currently evaluates to double -0.01", jsonObject.get( "negativeFraction" ).equals(BigDecimal.valueOf(-0.01))); - assertTrue( "tooManyZerosFraction currently evaluates to double 0.001", - jsonObject.get( "tooManyZerosFraction" ).equals(BigDecimal.valueOf(0.001))); - assertTrue( "tooManyZerosFraction currently evaluates to double 0.001", - jsonObject.getLong( "tooManyZerosFraction" )==0); assertTrue( "tooManyZerosFraction currently evaluates to double 0.001", jsonObject.optLong( "tooManyZerosFraction" )==0); assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875", @@ -1118,53 +1101,6 @@ public class JSONObjectTest { jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1))); assertTrue("doubleIdentifier currently evaluates to double 0.1", jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1))); - assertTrue("doubleIdentifierWithMultipleLeadingZerosBeforeDecimal currently evaluates to double 0.1", - jsonObject.get("doubleIdentifierWithMultipleLeadingZerosBeforeDecimal").equals(Double.valueOf(0.1))); - assertTrue("negativeDoubleIdentifierWithMultipleLeadingZerosBeforeDecimal currently evaluates to double -0.1", - jsonObject.get("negativeDoubleIdentifierWithMultipleLeadingZerosBeforeDecimal").equals(Double.valueOf(-0.1))); - assertTrue("doubleIdentifierWithMultipleLeadingZerosAfterDecimal currently evaluates to double 0.0001", - jsonObject.get("doubleIdentifierWithMultipleLeadingZerosAfterDecimal").equals(Double.valueOf(0.0001))); - assertTrue("doubleIdentifierWithMultipleLeadingZerosAfterDecimal currently evaluates to double 0.0001", - jsonObject.get("doubleIdentifierWithMultipleLeadingZerosAfterDecimal").equals(Double.valueOf(0.0001))); - assertTrue("negativeDoubleIdentifierWithMultipleLeadingZerosAfterDecimal currently evaluates to double -0.0001", - jsonObject.get("negativeDoubleIdentifierWithMultipleLeadingZerosAfterDecimal").equals(Double.valueOf(-0.0001))); - assertTrue("Integer does not evaluate to 900", - jsonObject.get("integerWithLeadingZeros").equals(900)); - assertTrue("Integer does not evaluate to 900", - jsonObject.getInt("integerWithLeadingZeros")==900); - assertTrue("Integer does not evaluate to 900", - jsonObject.optInt("integerWithLeadingZeros")==900); - assertTrue("Integer does not evaluate to 0", - jsonObject.get("integerWithAllZeros").equals(0)); - assertTrue("Integer does not evaluate to 0", - jsonObject.getInt("integerWithAllZeros")==0); - assertTrue("Integer does not evaluate to 0", - jsonObject.optInt("integerWithAllZeros")==0); - assertTrue("Double does not evaluate to 800.90", - jsonObject.get("compositeWithLeadingZeros").equals(800.90)); - assertTrue("Double does not evaluate to 800.90", - jsonObject.getDouble("compositeWithLeadingZeros")==800.9d); - assertTrue("Integer does not evaluate to 800", - jsonObject.optInt("compositeWithLeadingZeros")==800); - assertTrue("Long does not evaluate to 800.90", - jsonObject.getLong("compositeWithLeadingZeros")==800); - assertTrue("Long does not evaluate to 800.90", - jsonObject.optLong("compositeWithLeadingZeros")==800); - assertEquals("Get long of decimalPositiveWithoutNumberBeforeDecimalPoint does not match", - 0.9d,jsonObject.getDouble("decimalPositiveWithoutNumberBeforeDecimalPoint"), 0.0d); - assertEquals("Get long of decimalPositiveWithoutNumberBeforeDecimalPoint does not match", - 0.9d,jsonObject.optDouble("decimalPositiveWithoutNumberBeforeDecimalPoint"), 0.0d); - assertEquals("Get long of decimalPositiveWithoutNumberBeforeDecimalPoint does not match", - 0.0d,jsonObject.optLong("decimalPositiveWithoutNumberBeforeDecimalPoint"), 0.0d); - - assertEquals("Get long of doubleIdentifierWithMultipleLeadingZerosAfterDecimal does not match", - 0.0001d,jsonObject.getDouble("doubleIdentifierWithMultipleLeadingZerosAfterDecimal"), 0.0d); - assertEquals("Get long of doubleIdentifierWithMultipleLeadingZerosAfterDecimal does not match", - 0.0001d,jsonObject.optDouble("doubleIdentifierWithMultipleLeadingZerosAfterDecimal"), 0.0d); - assertEquals("Get long of doubleIdentifierWithMultipleLeadingZerosAfterDecimal does not match", - 0.0d, jsonObject.getLong("doubleIdentifierWithMultipleLeadingZerosAfterDecimal") , 0.0d); - assertEquals("Get long of doubleIdentifierWithMultipleLeadingZerosAfterDecimal does not match", - 0.0d,jsonObject.optLong("doubleIdentifierWithMultipleLeadingZerosAfterDecimal"), 0.0d); Util.checkJSONObjectMaps(jsonObject); } @@ -2398,7 +2334,7 @@ public class JSONObjectTest { } try { // test validity of invalid double - JSONObject.testValidity(NaN); + JSONObject.testValidity(Double.NaN); fail("Expected an exception"); } catch (JSONException e) { assertTrue("", true); diff --git a/src/test/java/org/json/junit/JsonNumberZeroTest.java b/src/test/java/org/json/junit/JsonNumberZeroTest.java deleted file mode 100644 index bfa4ca9..0000000 --- a/src/test/java/org/json/junit/JsonNumberZeroTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.json.junit; - -import org.json.JSONObject; -import org.junit.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import static org.junit.Assert.assertEquals; - -public class JsonNumberZeroTest { - - @Test - public void shouldParseNegativeZeroValueWithMultipleZeroDigit(){ - JSONObject jsonObject = new JSONObject("{value:-0000}"); - assertEquals("Float not recognized", -0f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", -0f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", -0f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", -0d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", -0.0d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", -0.0d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(-0).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - - @Test - public void shouldParseZeroValueWithMultipleZeroDigit(){ - JSONObject jsonObject = new JSONObject("{value:0000}"); - assertEquals("Float not recognized", 0f, jsonObject.getFloat("value"), 0.0f); - assertEquals("Float not recognized", 0f, jsonObject.optFloat("value"), 0.0f); - assertEquals("Float not recognized", 0f, jsonObject.optFloatObject("value"), 0.0f); - assertEquals("Double not recognized", 0d, jsonObject.optDouble("value"), 0.0f); - assertEquals("Double not recognized", 0.0d, jsonObject.optDoubleObject("value"), 0.0f); - assertEquals("Double not recognized", 0.0d, jsonObject.getDouble("value"), 0.0f); - assertEquals("Long not recognized", 0, jsonObject.optLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.getLong("value"), 0); - assertEquals("Long not recognized", 0, jsonObject.optLongObject("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.getInt("value"), 0); - assertEquals("Integer not recognized", 0, jsonObject.optIntegerObject("value"), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").intValue(), 0); - assertEquals("Number not recognized", 0, jsonObject.getNumber("value").longValue(), 0); - assertEquals("BigDecimal not recognized", 0, BigDecimal.valueOf(-0).compareTo(jsonObject.getBigDecimal("value"))); - assertEquals("BigInteger not recognized",0, BigInteger.valueOf(0).compareTo(jsonObject.getBigInteger("value"))); - } - -} diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java index ba8418c..e9714af 100755 --- a/src/test/java/org/json/junit/XMLConfigurationTest.java +++ b/src/test/java/org/json/junit/XMLConfigurationTest.java @@ -761,7 +761,7 @@ public class XMLConfigurationTest { @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; - final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); + final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}"); final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, new XMLParserConfiguration().withKeepStrings(false)); Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected); diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 9ae1ee2..db90592 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -791,7 +791,7 @@ public class XMLTest { @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; - final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); + final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}"); final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false); Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson); From 898288810fb55f06dc6e046212f00dcd639773c3 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 24 Feb 2024 21:07:12 -0600 Subject: [PATCH 11/18] add unit tests to clarify current behavior for JSONObject and XML --- .../java/org/json/junit/JSONObjectTest.java | 37 +++++++++++++++++++ src/test/java/org/json/junit/XMLTest.java | 15 ++++++++ 2 files changed, 52 insertions(+) diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index d90297d..fac8c53 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -3738,6 +3738,43 @@ public class JSONObjectTest { new JSONObject(map1); } + @Test + public void clarifyCurrentBehavior() { + // Behavior documented in #653 optLong vs getLong inconsistencies + // This problem still exists. + // Internally, both number_1 and number_2 are stored as strings. This is reasonable since they are parsed as strings. + // However, getLong and optLong should return similar results + JSONObject json = new JSONObject("{\"number_1\":\"01234\", \"number_2\": \"332211\"}"); + assertEquals(json.getLong("number_1"), 1234L); + assertEquals(json.optLong("number_1"), 0); //THIS VALUE IS NOT RETURNED AS A NUMBER + assertEquals(json.getLong("number_2"), 332211L); + assertEquals(json.optLong("number_2"), 332211L); + + // Behavior documented in #826 JSONObject parsing 0-led numeric strings as ints + // After reverting the code, personId is stored as a string, and the behavior is as expected + String personId = "0123"; + JSONObject j1 = new JSONObject("{personId: " + personId + "}"); + assertEquals(j1.getString("personId"), "0123"); + + // Also #826. Here is input with missing quotes. Because of the leading zero, it should not be parsed as a number. + // This example was mentioned in the same ticket + // After reverting the code, personId is stored as a string, and the behavior is as expected + JSONObject j2 = new JSONObject("{\"personId\":0123}"); + assertEquals(j2.getString("personId"), "0123"); + + // Behavior uncovered while working on the code + // All of the values are stored as strings except for hex4, which is stored as a number. This is probably incorrect + JSONObject j3 = new JSONObject("{ " + + "\"hex1\": \"010e4\", \"hex2\": \"00f0\", \"hex3\": \"0011\", " + + "\"hex4\": 00e0, \"hex5\": 00f0, \"hex6\": 0011 }"); + assertEquals(j3.getString("hex1"), "010e4"); + assertEquals(j3.getString("hex2"), "00f0"); + assertEquals(j3.getString("hex3"), "0011"); + assertEquals(j3.getLong("hex4"), 0, .1); + assertEquals(j3.getString("hex5"), "00f0"); + assertEquals(j3.getString("hex6"), "0011"); + } + /** * Method to build nested map of max maxDepth * diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index db90592..9bb3d9f 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1397,6 +1397,21 @@ public class XMLTest { Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } + @Test + public void clarifyCurrentBehavior() { + + // Behavior documented in #852 + // After reverting the code, value is still stored as a number. This is due to how XML.isDecimalNotation() works + // and is probably a bug. JSONObject has a similar problem. + String str = " primary 008E97 "; + JSONObject jsonObject = XML.toJSONObject(str); + assertEquals(jsonObject.getJSONObject("color").getLong("value"), 0e897, .1); + + // Workaround for now is to use keepStrings + JSONObject jsonObject1 = XML.toJSONObject(str, new XMLParserConfiguration().withKeepStrings(true)); + assertEquals(jsonObject1.getJSONObject("color").getString("value"), "008E97"); + } + } From d520210ea26883965506d04153557e101903754a Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 25 Feb 2024 10:45:34 -0600 Subject: [PATCH 12/18] Added one more example to XMLTest clarifyCurrentBehavior() --- src/test/java/org/json/junit/XMLTest.java | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 9bb3d9f..3b26b22 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1400,16 +1400,30 @@ public class XMLTest { @Test public void clarifyCurrentBehavior() { + // Behavior documented in #826 + // After reverting the code, amount is stored as numeric, and phone is stored as string + String str1 = + " \n" + + " 0123456789\n" + + " 0.1230\n" + + " true\n" + + " "; + JSONObject jsonObject1 = XML.toJSONObject(str1, + new XMLParserConfiguration().withKeepStrings(false)); + assertEquals(jsonObject1.getJSONObject("datatypes").getFloat("amount"), 0.123, .1); + assertEquals(jsonObject1.getJSONObject("datatypes").getString("telephone"), "0123456789"); + + // Behavior documented in #852 // After reverting the code, value is still stored as a number. This is due to how XML.isDecimalNotation() works // and is probably a bug. JSONObject has a similar problem. - String str = " primary 008E97 "; - JSONObject jsonObject = XML.toJSONObject(str); - assertEquals(jsonObject.getJSONObject("color").getLong("value"), 0e897, .1); + String str2 = " primary 008E97 "; + JSONObject jsonObject2 = XML.toJSONObject(str2); + assertEquals(jsonObject2.getJSONObject("color").getLong("value"), 0e897, .1); // Workaround for now is to use keepStrings - JSONObject jsonObject1 = XML.toJSONObject(str, new XMLParserConfiguration().withKeepStrings(true)); - assertEquals(jsonObject1.getJSONObject("color").getString("value"), "008E97"); + JSONObject jsonObject3 = XML.toJSONObject(str2, new XMLParserConfiguration().withKeepStrings(true)); + assertEquals(jsonObject3.getJSONObject("color").getString("value"), "008E97"); } } From 8de0628bd11912cbcaf11da566751f6396e096fe Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 2 Mar 2024 08:55:24 -0600 Subject: [PATCH 13/18] pipeline-updates - disable deployment.yml workflow for now (it's not set up in secrets yet) --- .github/workflows/deployment.yml | 153 ++++++++++++++++--------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index e870644..8cda38e 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -4,79 +4,80 @@ # * https://github.com/actions/setup-java/blob/v3.13.0/docs/advanced-usage.md#Publishing-using-Apache-Maven # -name: Deployment workflow - -on: - release: - types: [published] - -jobs: - # old-school build and jar method. No tests run or compiled. - publish-1_6: - name: Publish Java 1.6 to GitHub Release - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - name: Setup java - uses: actions/setup-java@v1 - with: - java-version: 1.6 - - name: Compile Java 1.6 - run: | - mkdir -p target/classes - javac -version - javac -source 1.6 -target 1.6 -d target/classes/ src/main/java/org/json/*.java - - name: Create JAR 1.6 - run: | - jar cvf "target/org.json-1.6-${{ github.ref_name }}.jar" -C target/classes . - - name: Add 1.6 Jar To Release - uses: softprops/action-gh-release@v1 - with: - append_body: true - files: | - target/*.jar - publish: - name: Publish Java 8 to Maven Central and GitHub Release - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - steps: - - uses: actions/checkout@v4 - - name: Set up Java for publishing to Maven Central Repository - uses: actions/setup-java@v3 - with: - # Use lowest supported LTS Java version - java-version: '8' - distribution: 'temurin' - server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml - server-username: MAVEN_USERNAME # env variable for username in deploy - server-password: MAVEN_PASSWORD # env variable for token in deploy - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import - gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - - - name: Publish to the Maven Central Repository - run: mvn --batch-mode deploy - env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - - name: Add Jar To Release - uses: softprops/action-gh-release@v1 - with: - append_body: true - files: | - target/*.jar - # - name: Set up Java for publishing to GitHub Packages - # uses: actions/setup-java@v3 - # with: - # # Use lowest supported LTS Java version - # java-version: '8' - # distribution: 'temurin' - # - name: Publish to GitHub Packages - # run: mvn --batch-mode deploy - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# COMMENTING OUT UNTIL SECRETS CAN BE ADDED +#name: Deployment workflow +# +#on: +# release: +# types: [published] +# +#jobs: +# # old-school build and jar method. No tests run or compiled. +# publish-1_6: +# name: Publish Java 1.6 to GitHub Release +# runs-on: ubuntu-latest +# permissions: +# contents: write +# steps: +# - uses: actions/checkout@v4 +# - name: Setup java +# uses: actions/setup-java@v1 +# with: +# java-version: 1.6 +# - name: Compile Java 1.6 +# run: | +# mkdir -p target/classes +# javac -version +# javac -source 1.6 -target 1.6 -d target/classes/ src/main/java/org/json/*.java +# - name: Create JAR 1.6 +# run: | +# jar cvf "target/org.json-1.6-${{ github.ref_name }}.jar" -C target/classes . +# - name: Add 1.6 Jar To Release +# uses: softprops/action-gh-release@v1 +# with: +# append_body: true +# files: | +# target/*.jar +# publish: +# name: Publish Java 8 to Maven Central and GitHub Release +# runs-on: ubuntu-latest +# permissions: +# contents: write +# packages: write +# steps: +# - uses: actions/checkout@v4 +# - name: Set up Java for publishing to Maven Central Repository +# uses: actions/setup-java@v3 +# with: +# # Use lowest supported LTS Java version +# java-version: '8' +# distribution: 'temurin' +# server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml +# server-username: MAVEN_USERNAME # env variable for username in deploy +# server-password: MAVEN_PASSWORD # env variable for token in deploy +# gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import +# gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase +# +# - name: Publish to the Maven Central Repository +# run: mvn --batch-mode deploy +# env: +# MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} +# MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} +# MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} +# +# - name: Add Jar To Release +# uses: softprops/action-gh-release@v1 +# with: +# append_body: true +# files: | +# target/*.jar +# # - name: Set up Java for publishing to GitHub Packages +# # uses: actions/setup-java@v3 +# # with: +# # # Use lowest supported LTS Java version +# # java-version: '8' +# # distribution: 'temurin' +# # - name: Publish to GitHub Packages +# # run: mvn --batch-mode deploy +# # env: +# # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 989cdb61bcf46ad8a9d3759ec4c65ed2956330d3 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 2 Mar 2024 09:15:32 -0600 Subject: [PATCH 14/18] pipeline-updates - do not build in parallel --- .github/workflows/pipeline.yml | 103 +++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 7350f7a..bb4cf07 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -35,6 +35,54 @@ jobs: name: Create java 1.6 JAR path: target/*.jar + build-8: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 1 + matrix: + # build against supported Java LTS versions: + java: [ 8 ] + name: Java ${{ matrix.java }} + steps: + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + - name: Compile Java ${{ matrix.java }} + run: mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + - name: Run Tests ${{ matrix.java }} + run: | + mvn test -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Build Test Report ${{ matrix.java }} + if: ${{ always() }} + run: | + mvn surefire-report:report-only -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Upload Test Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Results ${{ matrix.java }} + path: target/surefire-reports/ + - name: Upload Test Report ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Report ${{ matrix.java }} + path: target/site/ + - name: Package Jar ${{ matrix.java }} + run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true + - name: Upload Package Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Package Jar ${{ matrix.java }} + path: target/*.jar + build-11: runs-on: ubuntu-latest strategy: @@ -83,15 +131,62 @@ jobs: name: Package Jar ${{ matrix.java }} path: target/*.jar - - build-matrix: + build-17: runs-on: ubuntu-latest strategy: fail-fast: false - max-parallel: 2 + max-parallel: 1 matrix: # build against supported Java LTS versions: - java: [ 8, 17, 21 ] + java: [ 17 ] + name: Java ${{ matrix.java }} + steps: + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + - name: Compile Java ${{ matrix.java }} + run: mvn clean compile -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true -D maven.javadoc.skip=true + - name: Run Tests ${{ matrix.java }} + run: | + mvn test -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Build Test Report ${{ matrix.java }} + if: ${{ always() }} + run: | + mvn surefire-report:report-only -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} + - name: Upload Test Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Results ${{ matrix.java }} + path: target/surefire-reports/ + - name: Upload Test Report ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Test Report ${{ matrix.java }} + path: target/site/ + - name: Package Jar ${{ matrix.java }} + run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true + - name: Upload Package Results ${{ matrix.java }} + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: Package Jar ${{ matrix.java }} + path: target/*.jar + + build-21: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 1 + matrix: + # build against supported Java LTS versions: + java: [ 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v3 From 3eb8a62af614c2507c6027c55414c15c93a197f2 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 2 Mar 2024 09:57:40 -0600 Subject: [PATCH 15/18] pipeline-updates - space after # char? --- .github/workflows/deployment.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 8cda38e..35a74f5 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -3,15 +3,15 @@ # * https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions # * https://github.com/actions/setup-java/blob/v3.13.0/docs/advanced-usage.md#Publishing-using-Apache-Maven # - +# # COMMENTING OUT UNTIL SECRETS CAN BE ADDED -#name: Deployment workflow +# name: Deployment workflow # -#on: +# on: # release: # types: [published] # -#jobs: +# jobs: # # old-school build and jar method. No tests run or compiled. # publish-1_6: # name: Publish Java 1.6 to GitHub Release From 390d442054c1591c895710d4df8da024dd95fe0a Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 2 Mar 2024 10:00:13 -0600 Subject: [PATCH 16/18] pipeline-updates - remove deployment.yml for now, will restore after setting up secrets --- .github/workflows/deployment.yml | 83 -------------------------------- 1 file changed, 83 deletions(-) delete mode 100644 .github/workflows/deployment.yml diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml deleted file mode 100644 index 35a74f5..0000000 --- a/.github/workflows/deployment.yml +++ /dev/null @@ -1,83 +0,0 @@ -# For more information see: -# * https://docs.github.com/en/actions/learn-github-actions -# * https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions -# * https://github.com/actions/setup-java/blob/v3.13.0/docs/advanced-usage.md#Publishing-using-Apache-Maven -# -# -# COMMENTING OUT UNTIL SECRETS CAN BE ADDED -# name: Deployment workflow -# -# on: -# release: -# types: [published] -# -# jobs: -# # old-school build and jar method. No tests run or compiled. -# publish-1_6: -# name: Publish Java 1.6 to GitHub Release -# runs-on: ubuntu-latest -# permissions: -# contents: write -# steps: -# - uses: actions/checkout@v4 -# - name: Setup java -# uses: actions/setup-java@v1 -# with: -# java-version: 1.6 -# - name: Compile Java 1.6 -# run: | -# mkdir -p target/classes -# javac -version -# javac -source 1.6 -target 1.6 -d target/classes/ src/main/java/org/json/*.java -# - name: Create JAR 1.6 -# run: | -# jar cvf "target/org.json-1.6-${{ github.ref_name }}.jar" -C target/classes . -# - name: Add 1.6 Jar To Release -# uses: softprops/action-gh-release@v1 -# with: -# append_body: true -# files: | -# target/*.jar -# publish: -# name: Publish Java 8 to Maven Central and GitHub Release -# runs-on: ubuntu-latest -# permissions: -# contents: write -# packages: write -# steps: -# - uses: actions/checkout@v4 -# - name: Set up Java for publishing to Maven Central Repository -# uses: actions/setup-java@v3 -# with: -# # Use lowest supported LTS Java version -# java-version: '8' -# distribution: 'temurin' -# server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml -# server-username: MAVEN_USERNAME # env variable for username in deploy -# server-password: MAVEN_PASSWORD # env variable for token in deploy -# gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import -# gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase -# -# - name: Publish to the Maven Central Repository -# run: mvn --batch-mode deploy -# env: -# MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} -# MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} -# MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} -# -# - name: Add Jar To Release -# uses: softprops/action-gh-release@v1 -# with: -# append_body: true -# files: | -# target/*.jar -# # - name: Set up Java for publishing to GitHub Packages -# # uses: actions/setup-java@v3 -# # with: -# # # Use lowest supported LTS Java version -# # java-version: '8' -# # distribution: 'temurin' -# # - name: Publish to GitHub Packages -# # run: mvn --batch-mode deploy -# # env: -# # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3d69990ab56185fef67c2b95a1af3379e679dac1 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sun, 3 Mar 2024 08:47:53 -0600 Subject: [PATCH 17/18] 20240303-pre-release-updates updates for release --- README.md | 2 +- docs/RELEASES.md | 2 ++ pom.xml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d17373..e46d257 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ JSON in Java [package org.json] [![Java CI with Maven](https://github.com/stleary/JSON-java/actions/workflows/pipeline.yml/badge.svg)](https://github.com/stleary/JSON-java/actions/workflows/pipeline.yml) [![CodeQL](https://github.com/stleary/JSON-java/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/stleary/JSON-java/actions/workflows/codeql-analysis.yml) -**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20240205/json-20240205.jar)** +**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20240205/json-20240303.jar)** # Overview diff --git a/docs/RELEASES.md b/docs/RELEASES.md index 3308e6e..30b8af2 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -5,6 +5,8 @@ and artifactId "json". For example: [https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav) ~~~ +20240303 Revert optLong/getLong changes, and recent commits. + 20240205 Recent commits. 20231013 First release with minimum Java version 1.8. Recent commits, including fixes for CVE-2023-5072. diff --git a/pom.xml b/pom.xml index 7196978..7b10243 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.json json - 20240205 + 20240303 bundle JSON in Java From dab29ec1d537c3f2f124fe1505f56b9756b45b33 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Sat, 9 Mar 2024 09:15:53 -0600 Subject: [PATCH 18/18] remove-jsonparserconfig-ctor - just use the withOverwriteDuplicateKey() method --- .../java/org/json/JSONParserConfiguration.java | 16 +++------------- .../json/junit/JSONParserConfigurationTest.java | 3 ++- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index fc16f61..190daeb 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -13,24 +13,14 @@ public class JSONParserConfiguration extends ParserConfiguration { * Configuration with the default values. */ public JSONParserConfiguration() { - this(false); - } - - /** - * Configure the parser with argument overwriteDuplicateKey. - * - * @param overwriteDuplicateKey Indicate whether to overwrite duplicate key or not.
- * If not, the JSONParser will throw a {@link JSONException} - * when meeting duplicate keys. - */ - public JSONParserConfiguration(boolean overwriteDuplicateKey) { super(); - this.overwriteDuplicateKey = overwriteDuplicateKey; + this.overwriteDuplicateKey = false; } @Override protected JSONParserConfiguration clone() { - JSONParserConfiguration clone = new JSONParserConfiguration(overwriteDuplicateKey); + JSONParserConfiguration clone = new JSONParserConfiguration(); + clone.overwriteDuplicateKey = overwriteDuplicateKey; clone.maxNestingDepth = maxNestingDepth; return clone; } diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java index 0e80d77..509b988 100644 --- a/src/test/java/org/json/junit/JSONParserConfigurationTest.java +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -18,7 +18,8 @@ public class JSONParserConfigurationTest { @Test public void testOverwrite() { - JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration(true)); + JSONObject jsonObject = new JSONObject(TEST_SOURCE, + new JSONParserConfiguration().withOverwriteDuplicateKey(true)); assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); }