Issue #240 - added support for "reject duplicate keys" config option (#241)
added option: REJECT_DUPLICATE_KEYS
Signed-off-by: John T.E. Timm <johntimm@us.ibm.com>
diff --git a/impl/src/main/java/org/glassfish/json/JsonBuilderFactoryImpl.java b/impl/src/main/java/org/glassfish/json/JsonBuilderFactoryImpl.java
index 3498e9a..7fde7a3 100644
--- a/impl/src/main/java/org/glassfish/json/JsonBuilderFactoryImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonBuilderFactoryImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -33,25 +33,27 @@
class JsonBuilderFactoryImpl implements JsonBuilderFactory {
private final Map<String, ?> config;
private final BufferPool bufferPool;
+ private final boolean rejectDuplicateKeys;
- JsonBuilderFactoryImpl(BufferPool bufferPool) {
+ JsonBuilderFactoryImpl(BufferPool bufferPool, boolean rejectDuplicateKeys) {
this.config = Collections.emptyMap();
this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
}
@Override
public JsonObjectBuilder createObjectBuilder() {
- return new JsonObjectBuilderImpl(bufferPool);
+ return new JsonObjectBuilderImpl(bufferPool, rejectDuplicateKeys);
}
@Override
public JsonObjectBuilder createObjectBuilder(JsonObject object) {
- return new JsonObjectBuilderImpl(object, bufferPool);
+ return new JsonObjectBuilderImpl(object, bufferPool, rejectDuplicateKeys);
}
@Override
public JsonObjectBuilder createObjectBuilder(Map<String, Object> object) {
- return new JsonObjectBuilderImpl(object, bufferPool);
+ return new JsonObjectBuilderImpl(object, bufferPool, rejectDuplicateKeys);
}
@Override
diff --git a/impl/src/main/java/org/glassfish/json/JsonMessages.java b/impl/src/main/java/org/glassfish/json/JsonMessages.java
index 219b507..aeec4e4 100644
--- a/impl/src/main/java/org/glassfish/json/JsonMessages.java
+++ b/impl/src/main/java/org/glassfish/json/JsonMessages.java
@@ -116,6 +116,10 @@
static String PARSER_INPUT_ENC_DETECT_IOERR() {
return localize("parser.input.enc.detect.ioerr");
}
+
+ static String DUPLICATE_KEY(String name) {
+ return localize("parser.duplicate.key", name);
+ }
// generator messages
static String GENERATOR_FLUSH_IO_ERR() {
diff --git a/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java b/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
index 9d20e4a..1104055 100644
--- a/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -18,7 +18,6 @@
import org.glassfish.json.api.BufferPool;
-import jakarta.json.JsonArrayBuilder;
import jakarta.json.*;
import java.io.StringWriter;
import java.math.BigDecimal;
@@ -33,23 +32,46 @@
*/
class JsonObjectBuilderImpl implements JsonObjectBuilder {
- private Map<String, JsonValue> valueMap;
+ protected Map<String, JsonValue> valueMap;
private final BufferPool bufferPool;
+ private final boolean rejectDuplicateKeys;
JsonObjectBuilderImpl(BufferPool bufferPool) {
this.bufferPool = bufferPool;
+ rejectDuplicateKeys = false;
+ }
+
+ JsonObjectBuilderImpl(BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
}
JsonObjectBuilderImpl(JsonObject object, BufferPool bufferPool) {
this.bufferPool = bufferPool;
valueMap = new LinkedHashMap<>();
valueMap.putAll(object);
+ rejectDuplicateKeys = false;
+ }
+
+ JsonObjectBuilderImpl(JsonObject object, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ this.bufferPool = bufferPool;
+ valueMap = new LinkedHashMap<>();
+ valueMap.putAll(object);
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
}
JsonObjectBuilderImpl(Map<String, Object> map, BufferPool bufferPool) {
this.bufferPool = bufferPool;
valueMap = new LinkedHashMap<>();
populate(map);
+ rejectDuplicateKeys = false;
+ }
+
+ JsonObjectBuilderImpl(Map<String, Object> map, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ this.bufferPool = bufferPool;
+ valueMap = new LinkedHashMap<>();
+ populate(map);
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
}
@Override
@@ -184,7 +206,10 @@
if (valueMap == null) {
this.valueMap = new LinkedHashMap<>();
}
- valueMap.put(name, value);
+ JsonValue previousValue = valueMap.put(name, value);
+ if (rejectDuplicateKeys && previousValue != null) {
+ throw new IllegalStateException(JsonMessages.DUPLICATE_KEY(name));
+ }
}
private void validateName(String name) {
diff --git a/impl/src/main/java/org/glassfish/json/JsonParserImpl.java b/impl/src/main/java/org/glassfish/json/JsonParserImpl.java
index 23e86e7..62bf4d4 100644
--- a/impl/src/main/java/org/glassfish/json/JsonParserImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonParserImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,6 +23,7 @@
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.AbstractMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Spliterator;
@@ -55,25 +56,41 @@
public class JsonParserImpl implements JsonParser {
private final BufferPool bufferPool;
+ private final boolean rejectDuplicateKeys;
private Context currentContext = new NoneContext();
private Event currentEvent;
private final Stack stack = new Stack();
private final JsonTokenizer tokenizer;
-
+
public JsonParserImpl(Reader reader, BufferPool bufferPool) {
+ this(reader, bufferPool, false);
+ }
+
+ public JsonParserImpl(Reader reader, BufferPool bufferPool, boolean rejectDuplicateKeys) {
this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
tokenizer = new JsonTokenizer(reader, bufferPool);
}
public JsonParserImpl(InputStream in, BufferPool bufferPool) {
+ this(in, bufferPool, false);
+ }
+
+ public JsonParserImpl(InputStream in, BufferPool bufferPool, boolean rejectDuplicateKeys) {
this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
UnicodeDetectingInputStream uin = new UnicodeDetectingInputStream(in);
tokenizer = new JsonTokenizer(new InputStreamReader(uin, uin.getCharset()), bufferPool);
}
public JsonParserImpl(InputStream in, Charset encoding, BufferPool bufferPool) {
+ this(in, encoding, bufferPool, false);
+ }
+
+ public JsonParserImpl(InputStream in, Charset encoding, BufferPool bufferPool, boolean rejectDuplicateKeys) {
this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
tokenizer = new JsonTokenizer(new InputStreamReader(in, encoding), bufferPool);
}
@@ -110,7 +127,7 @@
}
boolean isDefinitelyLong() {
- return tokenizer.isDefinitelyLong();
+ return tokenizer.isDefinitelyLong();
}
@Override
@@ -146,7 +163,7 @@
throw new IllegalStateException(
JsonMessages.PARSER_GETOBJECT_ERR(currentEvent));
}
- return getObject(new JsonObjectBuilderImpl(bufferPool));
+ return getObject(new JsonObjectBuilderImpl(bufferPool, rejectDuplicateKeys));
}
@Override
@@ -175,7 +192,7 @@
case END_ARRAY:
case END_OBJECT:
default:
- throw new IllegalStateException(JsonMessages.PARSER_GETVALUE_ERR(currentEvent));
+ throw new IllegalStateException(JsonMessages.PARSER_GETVALUE_ERR(currentEvent));
}
}
diff --git a/impl/src/main/java/org/glassfish/json/JsonProviderImpl.java b/impl/src/main/java/org/glassfish/json/JsonProviderImpl.java
index ce3627b..a4ebc8a 100644
--- a/impl/src/main/java/org/glassfish/json/JsonProviderImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonProviderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -17,6 +17,7 @@
package org.glassfish.json;
import org.glassfish.json.api.BufferPool;
+import org.glassfish.json.api.JsonConfig;
import jakarta.json.*;
import jakarta.json.stream.JsonGenerator;
@@ -41,7 +42,6 @@
* @author Alex Soto
*/
public class JsonProviderImpl extends JsonProvider {
-
private final BufferPool bufferPool = new BufferPoolImpl();
@Override
@@ -149,14 +149,27 @@
@Override
public JsonReaderFactory createReaderFactory(Map<String, ?> config) {
- BufferPool pool = null;
- if (config != null && config.containsKey(BufferPool.class.getName())) {
- pool = (BufferPool)config.get(BufferPool.class.getName());
- }
- if (pool == null) {
+ Map<String, Object> providerConfig;
+ boolean rejectDuplicateKeys;
+ BufferPool pool;
+ if (config == null) {
+ providerConfig = Collections.emptyMap();
+ rejectDuplicateKeys = false;
pool = bufferPool;
+ } else {
+ providerConfig = new HashMap<>();
+ if (rejectDuplicateKeys = JsonProviderImpl.isRejectDuplicateKeysEnabled(config)) {
+ providerConfig.put(JsonConfig.REJECT_DUPLICATE_KEYS, true);
+ }
+ pool = (BufferPool) config.get(BufferPool.class.getName());
+ if (pool != null) {
+ providerConfig.put(BufferPool.class.getName(), pool);
+ } else {
+ pool = bufferPool;
+ }
+ providerConfig = Collections.unmodifiableMap(providerConfig);
}
- return new JsonReaderFactoryImpl(pool);
+ return new JsonReaderFactoryImpl(providerConfig, pool, rejectDuplicateKeys);
}
@Override
@@ -255,18 +268,23 @@
}
@Override
- public JsonBuilderFactory createBuilderFactory(Map<String,?> config) {
- BufferPool pool = null ;
- if (config != null && config.containsKey(BufferPool.class.getName())) {
- pool = (BufferPool)config.get(BufferPool.class.getName());
- }
- if (pool == null) {
- pool = bufferPool;
- }
- return new JsonBuilderFactoryImpl(pool);
+ public JsonBuilderFactory createBuilderFactory(Map<String, ?> config) {
+ BufferPool pool = bufferPool;
+ boolean rejectDuplicateKeys = false;
+ if (config != null) {
+ if (config.containsKey(BufferPool.class.getName())) {
+ pool = (BufferPool) config.get(BufferPool.class.getName());
+ }
+ rejectDuplicateKeys = JsonProviderImpl.isRejectDuplicateKeysEnabled(config);
+ }
+ return new JsonBuilderFactoryImpl(pool, rejectDuplicateKeys);
}
static boolean isPrettyPrintingEnabled(Map<String, ?> config) {
return config.containsKey(JsonGenerator.PRETTY_PRINTING);
}
+
+ static boolean isRejectDuplicateKeysEnabled(Map<String, ?> config) {
+ return config.containsKey(JsonConfig.REJECT_DUPLICATE_KEYS);
+ }
}
diff --git a/impl/src/main/java/org/glassfish/json/JsonReaderFactoryImpl.java b/impl/src/main/java/org/glassfish/json/JsonReaderFactoryImpl.java
index 464fc8e..b039555 100644
--- a/impl/src/main/java/org/glassfish/json/JsonReaderFactoryImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonReaderFactoryImpl.java
@@ -23,33 +23,35 @@
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
-import java.util.Collections;
import java.util.Map;
/**
* @author Jitendra Kotamraju
*/
class JsonReaderFactoryImpl implements JsonReaderFactory {
- private final Map<String, ?> config = Collections.emptyMap();
+ private final Map<String, ?> config;
private final BufferPool bufferPool;
+ private final boolean rejectDuplicateKeys;
- JsonReaderFactoryImpl(BufferPool bufferPool) {
+ JsonReaderFactoryImpl(Map<String, ?> config, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ this.config = config;
this.bufferPool = bufferPool;
+ this.rejectDuplicateKeys = rejectDuplicateKeys;
}
@Override
public JsonReader createReader(Reader reader) {
- return new JsonReaderImpl(reader, bufferPool);
+ return new JsonReaderImpl(reader, bufferPool, rejectDuplicateKeys);
}
@Override
public JsonReader createReader(InputStream in) {
- return new JsonReaderImpl(in, bufferPool);
+ return new JsonReaderImpl(in, bufferPool, rejectDuplicateKeys);
}
@Override
public JsonReader createReader(InputStream in, Charset charset) {
- return new JsonReaderImpl(in, charset, bufferPool);
+ return new JsonReaderImpl(in, charset, bufferPool, rejectDuplicateKeys);
}
@Override
diff --git a/impl/src/main/java/org/glassfish/json/JsonReaderImpl.java b/impl/src/main/java/org/glassfish/json/JsonReaderImpl.java
index 81b1e0a..ee73531 100644
--- a/impl/src/main/java/org/glassfish/json/JsonReaderImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonReaderImpl.java
@@ -39,19 +39,31 @@
private final JsonParserImpl parser;
private boolean readDone;
private final BufferPool bufferPool;
-
+
JsonReaderImpl(Reader reader, BufferPool bufferPool) {
- parser = new JsonParserImpl(reader, bufferPool);
+ this(reader, bufferPool, false);
+ }
+
+ JsonReaderImpl(Reader reader, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ parser = new JsonParserImpl(reader, bufferPool, rejectDuplicateKeys);
this.bufferPool = bufferPool;
}
JsonReaderImpl(InputStream in, BufferPool bufferPool) {
- parser = new JsonParserImpl(in, bufferPool);
+ this(in, bufferPool, false);
+ }
+
+ JsonReaderImpl(InputStream in, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ parser = new JsonParserImpl(in, bufferPool, rejectDuplicateKeys);
this.bufferPool = bufferPool;
}
JsonReaderImpl(InputStream in, Charset charset, BufferPool bufferPool) {
- parser = new JsonParserImpl(in, charset, bufferPool);
+ this(in, charset, bufferPool, false);
+ }
+
+ JsonReaderImpl(InputStream in, Charset charset, BufferPool bufferPool, boolean rejectDuplicateKeys) {
+ parser = new JsonParserImpl(in, charset, bufferPool, rejectDuplicateKeys);
this.bufferPool = bufferPool;
}
diff --git a/impl/src/main/java/org/glassfish/json/api/JsonConfig.java b/impl/src/main/java/org/glassfish/json/api/JsonConfig.java
new file mode 100644
index 0000000..593c7d2
--- /dev/null
+++ b/impl/src/main/java/org/glassfish/json/api/JsonConfig.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.json.api;
+
+public interface JsonConfig {
+ /**
+ * Configuration property to reject duplicate keys. The value of the property could be
+ * be anything.
+ */
+ String REJECT_DUPLICATE_KEYS = "org.glassfish.json.rejectDuplicateKeys";
+}
diff --git a/impl/src/main/resources/org/glassfish/json/messages.properties b/impl/src/main/resources/org/glassfish/json/messages.properties
index bcc877f..3e4c349 100644
--- a/impl/src/main/resources/org/glassfish/json/messages.properties
+++ b/impl/src/main/resources/org/glassfish/json/messages.properties
@@ -42,6 +42,7 @@
parser.scope.err=Cannot be called for value {0}
parser.input.enc.detect.failed=Cannot auto-detect encoding, not enough chars
parser.input.enc.detect.ioerr=I/O error while auto-detecting the encoding of stream
+parser.duplicate.key=Duplicate key ''{0}'' is not allowed
generator.flush.io.err=I/O error while flushing generated JSON
generator.close.io.err=I/O error while closing JsonGenerator
diff --git a/impl/src/test/java/org/glassfish/json/tests/JsonDuplicateKeyTest.java b/impl/src/test/java/org/glassfish/json/tests/JsonDuplicateKeyTest.java
new file mode 100644
index 0000000..c95d04a
--- /dev/null
+++ b/impl/src/test/java/org/glassfish/json/tests/JsonDuplicateKeyTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.json.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.StringReader;
+import java.util.Collections;
+
+import org.glassfish.json.api.JsonConfig;
+import org.junit.Test;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonReaderFactory;
+import jakarta.json.stream.JsonParsingException;
+
+public class JsonDuplicateKeyTest {
+ @Test
+ public void testJsonReaderDuplicateKey1() {
+ String json = "{\"a\":\"b\",\"a\":\"c\"}";
+ JsonReader jsonReader = Json.createReader(new StringReader(json));
+ JsonObject jsonObject = jsonReader.readObject();
+ assertEquals(jsonObject.getString("a"), "c");
+ }
+
+ @Test
+ public void testJsonReaderDuplicateKey2() {
+ String json = "{\"a\":\"b\",\"a\":\"c\"}";
+ JsonReaderFactory jsonReaderFactory = Json.createReaderFactory(Collections.singletonMap(JsonConfig.REJECT_DUPLICATE_KEYS, true));
+ JsonReader jsonReader = jsonReaderFactory.createReader(new StringReader(json));
+ try {
+ jsonReader.readObject();
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof JsonParsingException);
+ assertEquals("Duplicate key 'a' is not allowed", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testJsonObjectBuilderDuplcateKey1() {
+ JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+ JsonObject jsonObject = objectBuilder.add("a", "b").add("a", "c").build();
+ assertEquals(jsonObject.getString("a"), "c");
+ }
+
+ @Test
+ public void testJsonObjectBuilderDuplcateKey2() {
+ JsonBuilderFactory jsonBuilderFactory = Json.createBuilderFactory(Collections.singletonMap(JsonConfig.REJECT_DUPLICATE_KEYS, true));
+ JsonObjectBuilder objectBuilder = jsonBuilderFactory.createObjectBuilder();
+ try {
+ objectBuilder.add("a", "b").add("a", "c").build();
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalStateException);
+ assertEquals("Duplicate key 'a' is not allowed", e.getMessage());
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 4dda0bd..0bf38ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at