diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java
index 6494f93..8c0e0fc 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;
/**
@@ -205,6 +196,21 @@ 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;
@@ -234,13 +240,14 @@ public class JSONObject {
if (key != null) {
// Check if key exists
- if (this.opt(key) != null) {
- // key already exists
+ boolean keyExists = this.opt(key) != null;
+ if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw x.syntaxError("Duplicate key \"" + key + "\"");
}
- // Only add value if non-null
+
Object value = x.nextValue();
- if (value!=null) {
+ // Only add value if non-null
+ if (value != null) {
this.put(key, value);
}
}
@@ -296,7 +303,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()) {
@@ -427,7 +433,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..fc16f61 100644
--- a/src/main/java/org/json/JSONParserConfiguration.java
+++ b/src/main/java/org/json/JSONParserConfiguration.java
@@ -4,23 +4,77 @@ package org.json;
* Configuration object for the JSON parser. The configuration is immutable.
*/
public class JSONParserConfiguration extends ParserConfiguration {
+ /**
+ * Used to indicate whether to overwrite duplicate key or not.
+ */
+ private boolean overwriteDuplicateKey;
- /**
- * Configuration with the default values.
- */
- public JSONParserConfiguration() {
- super();
- }
+ /**
+ * Configuration with the default values.
+ */
+ public JSONParserConfiguration() {
+ this(false);
+ }
- @Override
- protected JSONParserConfiguration clone() {
- return new JSONParserConfiguration();
- }
+ /**
+ * 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;
+ }
- @SuppressWarnings("unchecked")
- @Override
- public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
- return super.withMaxNestingDepth(maxNestingDepth);
- }
+ @Override
+ protected JSONParserConfiguration clone() {
+ 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) {
+ 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 clone = this.clone();
+ clone.overwriteDuplicateKey = overwriteDuplicateKey;
+
+ 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/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());
+ }
+}