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