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"));
+ }
+}