Skip to content

Commit 712859d

Browse files
authored
Merge pull request stleary#857 from XIAYM-gh/master
Implemented custom duplicate key handling (stleary#840)
2 parents f9b5587 + 05b0897 commit 712859d

File tree

4 files changed

+170
-47
lines changed

4 files changed

+170
-47
lines changed

src/main/java/org/json/JSONObject.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,8 @@
1515
import java.lang.reflect.Modifier;
1616
import java.math.BigDecimal;
1717
import java.math.BigInteger;
18-
import java.util.Collection;
19-
import java.util.Collections;
20-
import java.util.Enumeration;
21-
import java.util.HashMap;
22-
import java.util.IdentityHashMap;
23-
import java.util.Iterator;
24-
import java.util.Locale;
25-
import java.util.Map;
18+
import java.util.*;
2619
import java.util.Map.Entry;
27-
import java.util.ResourceBundle;
28-
import java.util.Set;
2920
import java.util.regex.Pattern;
3021

3122
/**
@@ -205,6 +196,21 @@ public JSONObject(JSONObject jo, String ... names) {
205196
* duplicated key.
206197
*/
207198
public JSONObject(JSONTokener x) throws JSONException {
199+
this(x, new JSONParserConfiguration());
200+
}
201+
202+
/**
203+
* Construct a JSONObject from a JSONTokener with custom json parse configurations.
204+
*
205+
* @param x
206+
* A JSONTokener object containing the source string.
207+
* @param jsonParserConfiguration
208+
* Variable to pass parser custom configuration for json parsing.
209+
* @throws JSONException
210+
* If there is a syntax error in the source string or a
211+
* duplicated key.
212+
*/
213+
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
208214
this();
209215
char c;
210216
String key;
@@ -234,13 +240,14 @@ public JSONObject(JSONTokener x) throws JSONException {
234240

235241
if (key != null) {
236242
// Check if key exists
237-
if (this.opt(key) != null) {
238-
// key already exists
243+
boolean keyExists = this.opt(key) != null;
244+
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
239245
throw x.syntaxError("Duplicate key \"" + key + "\"");
240246
}
241-
// Only add value if non-null
247+
242248
Object value = x.nextValue();
243-
if (value!=null) {
249+
// Only add value if non-null
250+
if (value != null) {
244251
this.put(key, value);
245252
}
246253
}
@@ -296,7 +303,6 @@ public JSONObject(Map<?, ?> m, JSONParserConfiguration jsonParserConfiguration)
296303

297304
/**
298305
* Construct a JSONObject from a map with recursion depth.
299-
*
300306
*/
301307
private JSONObject(Map<?, ?> m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
302308
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
@@ -427,7 +433,25 @@ public JSONObject(Object object, String ... names) {
427433
* duplicated key.
428434
*/
429435
public JSONObject(String source) throws JSONException {
430-
this(new JSONTokener(source));
436+
this(source, new JSONParserConfiguration());
437+
}
438+
439+
/**
440+
* Construct a JSONObject from a source JSON text string with custom json parse configurations.
441+
* This is the most commonly used JSONObject constructor.
442+
*
443+
* @param source
444+
* A string beginning with <code>{</code>&nbsp;<small>(left
445+
* brace)</small> and ending with <code>}</code>
446+
* &nbsp;<small>(right brace)</small>.
447+
* @param jsonParserConfiguration
448+
* Variable to pass parser custom configuration for json parsing.
449+
* @exception JSONException
450+
* If there is a syntax error in the source string or a
451+
* duplicated key.
452+
*/
453+
public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
454+
this(new JSONTokener(source), jsonParserConfiguration);
431455
}
432456

433457
/**

src/main/java/org/json/JSONParserConfiguration.java

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,77 @@
44
* Configuration object for the JSON parser. The configuration is immutable.
55
*/
66
public class JSONParserConfiguration extends ParserConfiguration {
7+
/**
8+
* Used to indicate whether to overwrite duplicate key or not.
9+
*/
10+
private boolean overwriteDuplicateKey;
711

8-
/**
9-
* Configuration with the default values.
10-
*/
11-
public JSONParserConfiguration() {
12-
super();
13-
}
12+
/**
13+
* Configuration with the default values.
14+
*/
15+
public JSONParserConfiguration() {
16+
this(false);
17+
}
1418

15-
@Override
16-
protected JSONParserConfiguration clone() {
17-
return new JSONParserConfiguration();
18-
}
19+
/**
20+
* Configure the parser with argument overwriteDuplicateKey.
21+
*
22+
* @param overwriteDuplicateKey Indicate whether to overwrite duplicate key or not.<br>
23+
* If not, the JSONParser will throw a {@link JSONException}
24+
* when meeting duplicate keys.
25+
*/
26+
public JSONParserConfiguration(boolean overwriteDuplicateKey) {
27+
super();
28+
this.overwriteDuplicateKey = overwriteDuplicateKey;
29+
}
1930

20-
@SuppressWarnings("unchecked")
21-
@Override
22-
public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
23-
return super.withMaxNestingDepth(maxNestingDepth);
24-
}
31+
@Override
32+
protected JSONParserConfiguration clone() {
33+
JSONParserConfiguration clone = new JSONParserConfiguration(overwriteDuplicateKey);
34+
clone.maxNestingDepth = maxNestingDepth;
35+
return clone;
36+
}
2537

38+
/**
39+
* Defines the maximum nesting depth that the parser will descend before throwing an exception
40+
* when parsing a map into JSONObject or parsing a {@link java.util.Collection} instance into
41+
* JSONArray. The default max nesting depth is 512, which means the parser will throw a JsonException
42+
* if the maximum depth is reached.
43+
*
44+
* @param maxNestingDepth the maximum nesting depth allowed to the JSON parser
45+
* @return The existing configuration will not be modified. A new configuration is returned.
46+
*/
47+
@SuppressWarnings("unchecked")
48+
@Override
49+
public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
50+
JSONParserConfiguration clone = this.clone();
51+
clone.maxNestingDepth = maxNestingDepth;
52+
53+
return clone;
54+
}
55+
56+
/**
57+
* Controls the parser's behavior when meeting duplicate keys.
58+
* If set to false, the parser will throw a JSONException when meeting a duplicate key.
59+
* Or the duplicate key's value will be overwritten.
60+
*
61+
* @param overwriteDuplicateKey defines should the parser overwrite duplicate keys.
62+
* @return The existing configuration will not be modified. A new configuration is returned.
63+
*/
64+
public JSONParserConfiguration withOverwriteDuplicateKey(final boolean overwriteDuplicateKey) {
65+
JSONParserConfiguration clone = this.clone();
66+
clone.overwriteDuplicateKey = overwriteDuplicateKey;
67+
68+
return clone;
69+
}
70+
71+
/**
72+
* The parser's behavior when meeting duplicate keys, controls whether the parser should
73+
* overwrite duplicate keys or not.
74+
*
75+
* @return The <code>overwriteDuplicateKey</code> configuration value.
76+
*/
77+
public boolean isOverwriteDuplicateKey() {
78+
return this.overwriteDuplicateKey;
79+
}
2680
}

src/main/java/org/json/ParserConfiguration.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ public class ParserConfiguration {
2020

2121
/**
2222
* Specifies if values should be kept as strings (<code>true</code>), or if
23-
* they should try to be guessed into JSON values (numeric, boolean, string)
23+
* they should try to be guessed into JSON values (numeric, boolean, string).
2424
*/
2525
protected boolean keepStrings;
2626

2727
/**
28-
* The maximum nesting depth when parsing a document.
28+
* The maximum nesting depth when parsing an object.
2929
*/
3030
protected int maxNestingDepth;
3131

@@ -59,14 +59,14 @@ protected ParserConfiguration clone() {
5959
// map should be cloned as well. If the values of the map are known to also
6060
// be immutable, then a shallow clone of the map is acceptable.
6161
return new ParserConfiguration(
62-
this.keepStrings,
63-
this.maxNestingDepth
62+
this.keepStrings,
63+
this.maxNestingDepth
6464
);
6565
}
6666

6767
/**
6868
* When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
69-
* they should try to be guessed into JSON values (numeric, boolean, string)
69+
* they should try to be guessed into JSON values (numeric, boolean, string).
7070
*
7171
* @return The <code>keepStrings</code> configuration value.
7272
*/
@@ -78,22 +78,21 @@ public boolean isKeepStrings() {
7878
* When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
7979
* they should try to be guessed into JSON values (numeric, boolean, string)
8080
*
81-
* @param newVal
82-
* new value to use for the <code>keepStrings</code> configuration option.
83-
* @param <T> the type of the configuration object
84-
*
81+
* @param newVal new value to use for the <code>keepStrings</code> configuration option.
82+
* @param <T> the type of the configuration object
8583
* @return The existing configuration will not be modified. A new configuration is returned.
8684
*/
8785
@SuppressWarnings("unchecked")
8886
public <T extends ParserConfiguration> T withKeepStrings(final boolean newVal) {
89-
T newConfig = (T)this.clone();
87+
T newConfig = (T) this.clone();
9088
newConfig.keepStrings = newVal;
9189
return newConfig;
9290
}
9391

9492
/**
9593
* The maximum nesting depth that the parser will descend before throwing an exception
96-
* when parsing the XML into JSONML.
94+
* when parsing an object (e.g. Map, Collection) into JSON-related objects.
95+
*
9796
* @return the maximum nesting depth set for this configuration
9897
*/
9998
public int getMaxNestingDepth() {
@@ -102,18 +101,19 @@ public int getMaxNestingDepth() {
102101

103102
/**
104103
* Defines the maximum nesting depth that the parser will descend before throwing an exception
105-
* when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
106-
* will throw a JsonException if the maximum depth is reached.
104+
* when parsing an object (e.g. Map, Collection) into JSON-related objects.
105+
* The default max nesting depth is 512, which means the parser will throw a JsonException if
106+
* the maximum depth is reached.
107107
* Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
108108
* which means the parses will go as deep as the maximum call stack size allows.
109+
*
109110
* @param maxNestingDepth the maximum nesting depth allowed to the XML parser
110-
* @param <T> the type of the configuration object
111-
*
111+
* @param <T> the type of the configuration object
112112
* @return The existing configuration will not be modified. A new configuration is returned.
113113
*/
114114
@SuppressWarnings("unchecked")
115115
public <T extends ParserConfiguration> T withMaxNestingDepth(int maxNestingDepth) {
116-
T newConfig = (T)this.clone();
116+
T newConfig = (T) this.clone();
117117

118118
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
119119
newConfig.maxNestingDepth = maxNestingDepth;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.json.junit;
2+
3+
import org.json.JSONException;
4+
import org.json.JSONObject;
5+
import org.json.JSONParserConfiguration;
6+
import org.junit.Test;
7+
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertTrue;
10+
11+
public class JSONParserConfigurationTest {
12+
private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
13+
14+
@Test(expected = JSONException.class)
15+
public void testThrowException() {
16+
new JSONObject(TEST_SOURCE);
17+
}
18+
19+
@Test
20+
public void testOverwrite() {
21+
JSONObject jsonObject = new JSONObject(TEST_SOURCE, new JSONParserConfiguration(true));
22+
23+
assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
24+
}
25+
26+
@Test
27+
public void verifyDuplicateKeyThenMaxDepth() {
28+
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
29+
.withOverwriteDuplicateKey(true)
30+
.withMaxNestingDepth(42);
31+
32+
assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
33+
assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
34+
}
35+
36+
@Test
37+
public void verifyMaxDepthThenDuplicateKey() {
38+
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
39+
.withMaxNestingDepth(42)
40+
.withOverwriteDuplicateKey(true);
41+
42+
assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
43+
assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
44+
}
45+
}

0 commit comments

Comments
 (0)