mirror of
https://github.com/stleary/JSON-java.git
synced 2025-08-03 03:15:32 -04:00
Implemented custom duplicate key handling
- Supports: throw an exception (by default), ignore, overwrite & merge into a JSONArray - With tests, 4/4 passed.
This commit is contained in:
parent
010e83b925
commit
10514e48cb
28
src/main/java/org/json/JSONDuplicateKeyStrategy.java
Normal file
28
src/main/java/org/json/JSONDuplicateKeyStrategy.java
Normal file
@ -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.<br/>
|
||||
* 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
|
||||
}
|
@ -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<String> mergedKeys = null;
|
||||
|
||||
if (x.nextClean() != '{') {
|
||||
throw x.syntaxError("A JSONObject text must begin with '{'");
|
||||
@ -232,16 +242,47 @@ 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();
|
||||
}
|
||||
|
||||
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
|
||||
Object value = x.nextValue();
|
||||
if (value!=null) {
|
||||
if (value != null) {
|
||||
this.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pairs are separated by ','.
|
||||
|
||||
@ -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 <code>{</code> <small>(left
|
||||
* brace)</small> and ending with <code>}</code>
|
||||
* <small>(right brace)</small>.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,12 +4,26 @@ 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() {
|
||||
this(JSONDuplicateKeyStrategy.THROW_EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -23,4 +37,14 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
47
src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java
Normal file
47
src/test/java/org/json/junit/JSONObjectDuplicateKeyTest.java
Normal file
@ -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"));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user