This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new 6e33ee8 [JOHNZON-339] johnzon.rejectDuplicateKeys support 6e33ee8 is described below commit 6e33ee81dae81166a18407dd32aa5c75fd6456d5 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Tue Apr 6 20:07:44 2021 +0200 [JOHNZON-339] johnzon.rejectDuplicateKeys support --- .../apache/johnzon/core/AbstractJsonFactory.java | 8 +-- .../apache/johnzon/core/JohnzonJsonParserImpl.java | 6 +- .../apache/johnzon/core/JsonArrayBuilderImpl.java | 15 +++-- .../johnzon/core/JsonBuilderFactoryImpl.java | 38 ++++++------ .../apache/johnzon/core/JsonMergePatchDiff.java | 2 +- .../apache/johnzon/core/JsonMergePatchImpl.java | 2 +- .../apache/johnzon/core/JsonObjectBuilderImpl.java | 22 ++++--- .../org/apache/johnzon/core/JsonProviderImpl.java | 4 +- .../apache/johnzon/core/JsonReaderFactoryImpl.java | 18 +++--- .../org/apache/johnzon/core/JsonReaderImpl.java | 22 ++++--- .../johnzon/core/RejectDuplicateKeysMode.java | 71 ++++++++++++++++++++++ .../johnzon/core/JsonObjectBuilderImplTest.java | 9 +++ .../apache/johnzon/core/JsonReaderImplTest.java | 10 +++ 13 files changed, 165 insertions(+), 62 deletions(-) diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java b/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java index ded93e3..c851717 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java @@ -32,11 +32,11 @@ public abstract class AbstractJsonFactory implements Serializable { protected final Map<String, Object> internalConfig = new HashMap<String, Object>(); - protected AbstractJsonFactory(final Map<String, ?> config, Collection<String> supportedConfigKeys, Collection<String> defaultSupportedConfigKeys) { - if(config != null && config.size() > 0) { - + protected AbstractJsonFactory(final Map<String, ?> config, Collection<String> supportedConfigKeys, + final Collection<String> defaultSupportedConfigKeys) { + if(config != null && !config.isEmpty()) { if(defaultSupportedConfigKeys != null) { - supportedConfigKeys = new ArrayList<String>(supportedConfigKeys); + supportedConfigKeys = new ArrayList<>(supportedConfigKeys); supportedConfigKeys.addAll(defaultSupportedConfigKeys); } diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JohnzonJsonParserImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JohnzonJsonParserImpl.java index cf64a36..6edde73 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JohnzonJsonParserImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JohnzonJsonParserImpl.java @@ -59,7 +59,7 @@ public abstract class JohnzonJsonParserImpl implements JohnzonJsonParser { throw new IllegalStateException(current + " doesn't support getObject()"); } - JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider()); + JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT); return jsonReader.readObject(); } @@ -71,7 +71,7 @@ public abstract class JohnzonJsonParserImpl implements JohnzonJsonParser { throw new IllegalStateException(current + " doesn't support getArray()"); } - JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider()); + JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT); return jsonReader.readArray(); } @@ -81,7 +81,7 @@ public abstract class JohnzonJsonParserImpl implements JohnzonJsonParser { switch (current) { case START_ARRAY: case START_OBJECT: - JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider()); + JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT); return jsonReader.readValue(); case VALUE_TRUE: return JsonValue.TRUE; diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java index 5ea8c65..1ee44fc 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java @@ -35,6 +35,7 @@ import java.util.Map; import org.apache.johnzon.core.util.ArrayUtil; class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable { + private RejectDuplicateKeysMode rejectDuplicateKeysMode; private List<JsonValue> tmpList; private BufferStrategy.BufferProvider<char[]> bufferProvider; @@ -43,13 +44,17 @@ class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable { } public JsonArrayBuilderImpl(final JsonArray initialData, - final BufferStrategy.BufferProvider<char[]> provider) { + final BufferStrategy.BufferProvider<char[]> provider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { this.tmpList = new ArrayList<>(initialData); this.bufferProvider = provider; + this.rejectDuplicateKeysMode = rejectDuplicateKeysMode; } - public JsonArrayBuilderImpl(final Collection<?> initialData, final BufferStrategy.BufferProvider<char[]> provider) { + public JsonArrayBuilderImpl(final Collection<?> initialData, final BufferStrategy.BufferProvider<char[]> provider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { this.bufferProvider = provider; + this.rejectDuplicateKeysMode = rejectDuplicateKeysMode; this.tmpList = new ArrayList<>(); if (!initialData.isEmpty()) { for (Object initialValue : initialData) { @@ -220,12 +225,12 @@ class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable { } else if (value instanceof String) { add((String) value); } else if (value instanceof Map) { - add(new JsonObjectBuilderImpl(Map.class.cast(value), bufferProvider).build()); + add(new JsonObjectBuilderImpl(Map.class.cast(value), bufferProvider, rejectDuplicateKeysMode).build()); } else if (value instanceof Collection) { - add(new JsonArrayBuilderImpl(Collection.class.cast(value), bufferProvider).build()); + add(new JsonArrayBuilderImpl(Collection.class.cast(value), bufferProvider, rejectDuplicateKeysMode).build()); } else if (value.getClass().isArray()) { final Collection<Object> collection = ArrayUtil.newCollection(value); - add(new JsonArrayBuilderImpl(collection, bufferProvider).build()); + add(new JsonArrayBuilderImpl(collection, bufferProvider, rejectDuplicateKeysMode).build()); } else { throw new JsonException("Illegal JSON type! type=" + value.getClass()); } diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java index f0cb8f5..efd10fe 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java @@ -18,8 +18,12 @@ */ package org.apache.johnzon.core; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; import java.io.Serializable; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,32 +31,26 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; -import javax.json.JsonArray; -import javax.json.JsonArrayBuilder; -import javax.json.JsonBuilderFactory; -import javax.json.JsonObject; -import javax.json.JsonObjectBuilder; - import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; class JsonBuilderFactoryImpl implements JsonBuilderFactory, Serializable { private final Map<String, Object> internalConfig = new HashMap<String, Object>(); + private RejectDuplicateKeysMode rejectDuplicateKeysMode; private BufferStrategy.BufferProvider<char[]> bufferProvider; - private static final String[] SUPPORTED_CONFIG_KEYS = new String[] { - // nothing yet - }; + private static final List<String> SUPPORTED_CONFIG_KEYS = RejectDuplicateKeysMode.CONFIG_KEYS; protected JsonBuilderFactoryImpl() { // no-op: serialization } - JsonBuilderFactoryImpl(final Map<String, ?> config, final BufferStrategy.BufferProvider<char[]> bufferProvider) { + JsonBuilderFactoryImpl(final Map<String, ?> config, final BufferStrategy.BufferProvider<char[]> bufferProvider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { this.bufferProvider = bufferProvider; - if (config != null && config.size() > 0) { - final List<String> supportedConfigKeys = Arrays.asList(SUPPORTED_CONFIG_KEYS); + this.rejectDuplicateKeysMode = rejectDuplicateKeysMode; + if (config != null && !config.isEmpty()) { for (String configKey : config.keySet()) { - if(supportedConfigKeys.contains(configKey)) { + if(SUPPORTED_CONFIG_KEYS.contains(configKey)) { internalConfig.put(configKey, config.get(configKey)); } else { Logger.getLogger(this.getClass().getName()) @@ -64,28 +62,28 @@ class JsonBuilderFactoryImpl implements JsonBuilderFactory, Serializable { @Override public JsonObjectBuilder createObjectBuilder() { - return new JsonObjectBuilderImpl(emptyMap(), bufferProvider); + return new JsonObjectBuilderImpl(emptyMap(), bufferProvider, rejectDuplicateKeysMode); } @Override public JsonObjectBuilder createObjectBuilder(JsonObject initialData) { - return new JsonObjectBuilderImpl(initialData, bufferProvider); + return new JsonObjectBuilderImpl(initialData, bufferProvider, rejectDuplicateKeysMode); } @Override public JsonArrayBuilder createArrayBuilder() { - return new JsonArrayBuilderImpl(emptyList(), bufferProvider); + return new JsonArrayBuilderImpl(emptyList(), bufferProvider, rejectDuplicateKeysMode); } @Override public JsonArrayBuilder createArrayBuilder(JsonArray initialData) { - return new JsonArrayBuilderImpl(initialData, bufferProvider); + return new JsonArrayBuilderImpl(initialData, bufferProvider, rejectDuplicateKeysMode); } @Override public JsonArrayBuilder createArrayBuilder(Collection<?> initialData) { - return new JsonArrayBuilderImpl(initialData, bufferProvider); + return new JsonArrayBuilderImpl(initialData, bufferProvider, rejectDuplicateKeysMode); } @Override @@ -95,7 +93,7 @@ class JsonBuilderFactoryImpl implements JsonBuilderFactory, Serializable { @Override public JsonObjectBuilder createObjectBuilder(Map<String, Object> initialValues) { - return new JsonObjectBuilderImpl(initialValues, bufferProvider); + return new JsonObjectBuilderImpl(initialValues, bufferProvider, rejectDuplicateKeysMode); } } diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java index 1ca3bf9..3825229 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java @@ -45,7 +45,7 @@ class JsonMergePatchDiff extends DiffBase { } private JsonValue diff(JsonValue source, JsonValue target) { - JsonObjectBuilder builder = new JsonObjectBuilderImpl(emptyMap(), bufferProvider); + JsonObjectBuilder builder = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, RejectDuplicateKeysMode.DEFAULT); if (isJsonObject(source) && isJsonObject(target)) { JsonObject srcObj = source.asJsonObject(); diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java index 56518cd..9d40f05 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java @@ -58,7 +58,7 @@ public class JsonMergePatchImpl implements JsonMergePatch, Serializable { } private JsonValue applyJsonObjectPatch(JsonObject jsonObject, JsonObject patch) { - JsonObjectBuilder builder = new JsonObjectBuilderImpl(jsonObject, bufferProvider); + JsonObjectBuilder builder = new JsonObjectBuilderImpl(jsonObject, bufferProvider, RejectDuplicateKeysMode.DEFAULT); for (Map.Entry<String, JsonValue> patchAttrib : patch.entrySet()) { String attribName = patchAttrib.getKey(); diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java index fafcc74..2eb4ab0 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java @@ -36,6 +36,7 @@ import java.util.Map; import org.apache.johnzon.core.util.ArrayUtil; class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { + private RejectDuplicateKeysMode rejectDuplicateKeysMode; private BufferStrategy.BufferProvider<char[]> bufferProvider; private Map<String, JsonValue> attributeMap = new LinkedHashMap<>(); @@ -44,14 +45,18 @@ class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { } public JsonObjectBuilderImpl(final JsonObject initialData, - final BufferStrategy.BufferProvider<char[]> bufferProvider) { + final BufferStrategy.BufferProvider<char[]> bufferProvider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { this.bufferProvider = bufferProvider; - attributeMap = new LinkedHashMap<>(initialData); + this.rejectDuplicateKeysMode = rejectDuplicateKeysMode; + this.attributeMap = new LinkedHashMap<>(initialData); } public JsonObjectBuilderImpl(final Map<String, Object> initialValues, - final BufferStrategy.BufferProvider<char[]> bufferProvider) { + final BufferStrategy.BufferProvider<char[]> bufferProvider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { this.bufferProvider = bufferProvider; + this.rejectDuplicateKeysMode = rejectDuplicateKeysMode; if (!initialValues.isEmpty()) { for (Map.Entry<String, Object> entry : initialValues.entrySet()) { add(entry.getKey(), entry.getValue()); @@ -83,12 +88,12 @@ class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { } else if (value == null) { addNull(name); } else if (value instanceof Map) { - add(name, new JsonObjectBuilderImpl(Map.class.cast(value), bufferProvider).build()); + add(name, new JsonObjectBuilderImpl(Map.class.cast(value), bufferProvider, rejectDuplicateKeysMode).build()); } else if (value instanceof Collection) { - add(name, new JsonArrayBuilderImpl(Collection.class.cast(value), bufferProvider).build()); + add(name, new JsonArrayBuilderImpl(Collection.class.cast(value), bufferProvider, rejectDuplicateKeysMode).build()); } else if (value.getClass().isArray()) { final Collection<Object> collection = ArrayUtil.newCollection(value); - add(name, new JsonArrayBuilderImpl(collection, bufferProvider).build()); + add(name, new JsonArrayBuilderImpl(collection, bufferProvider, rejectDuplicateKeysMode).build()); } else { throw new JsonException("Illegal JSON type! name=" + name + " type=" + value.getClass()); } @@ -172,12 +177,11 @@ class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable { return this; } - private void putValue(String name, JsonValue value){ + private void putValue(final String name, final JsonValue value){ if(name == null || value == null) { throw new NullPointerException("name or value/builder must not be null"); } - - attributeMap.put(name, value); + rejectDuplicateKeysMode.put().put(attributeMap, name, value); } diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java index c3147d9..1bfcd71 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java @@ -68,7 +68,7 @@ public class JsonProviderImpl extends JsonProvider implements Serializable { private final JsonGeneratorFactory generatorFactory = new JsonGeneratorFactoryImpl(null); private final JsonWriterFactory writerFactory = new JsonWriterFactoryImpl(null); private final Supplier<JsonBuilderFactory> builderFactory = new Cached<>(() -> - new JsonBuilderFactoryImpl(null, bufferProvider.get())); + new JsonBuilderFactoryImpl(null, bufferProvider.get(), RejectDuplicateKeysMode.DEFAULT)); private final JsonPointerFactory jsonPointerFactory; public JsonProviderImpl() { @@ -201,7 +201,7 @@ public class JsonProviderImpl extends JsonProvider implements Serializable { public JsonBuilderFactory createBuilderFactory(final Map<String, ?> config) { final JsonBuilderFactory builderFactory = this.builderFactory.get(); return (config == null || config.isEmpty()) ? - builderFactory : new JsonBuilderFactoryImpl(config, bufferProvider.get()); + builderFactory : new JsonBuilderFactoryImpl(config, bufferProvider.get(), RejectDuplicateKeysMode.from(config)); } @Override diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java index 4f98812..233f8b0 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java @@ -18,8 +18,6 @@ */ package org.apache.johnzon.core; -import static java.util.Arrays.asList; - import java.io.InputStream; import java.io.Reader; import java.nio.charset.Charset; @@ -32,33 +30,37 @@ import javax.json.JsonReaderFactory; import javax.json.stream.JsonParser; class JsonReaderFactoryImpl extends AbstractJsonFactory implements JsonReaderFactory { - static final Collection<String> SUPPORTED_CONFIG_KEYS = asList( + static final Collection<String> SUPPORTED_CONFIG_KEYS = RejectDuplicateKeysMode.CONFIG_KEYS; - ); private final JsonParserFactoryImpl parserFactory; + private final RejectDuplicateKeysMode rejectDuplicateKeys; JsonReaderFactoryImpl(final Map<String, ?> config) { super(config, SUPPORTED_CONFIG_KEYS, JsonParserFactoryImpl.SUPPORTED_CONFIG_KEYS); + if (!internalConfig.isEmpty()) { + RejectDuplicateKeysMode.CONFIG_KEYS.forEach(internalConfig::remove); + } this.parserFactory = new JsonParserFactoryImpl(internalConfig); + this.rejectDuplicateKeys = RejectDuplicateKeysMode.from(config); } @Override public JsonReader createReader(final Reader reader) { - return new JsonReaderImpl(parserFactory.createInternalParser(reader), parserFactory.getValueBufferProvider()); + return new JsonReaderImpl(parserFactory.createInternalParser(reader), parserFactory.getValueBufferProvider(), rejectDuplicateKeys); } @Override public JsonReader createReader(final InputStream in) { - return new JsonReaderImpl(parserFactory.createInternalParser(in), parserFactory.getValueBufferProvider()); + return new JsonReaderImpl(parserFactory.createInternalParser(in), parserFactory.getValueBufferProvider(), rejectDuplicateKeys); } @Override public JsonReader createReader(final InputStream in, final Charset charset) { - return new JsonReaderImpl(parserFactory.createInternalParser(in, charset), parserFactory.getValueBufferProvider()); + return new JsonReaderImpl(parserFactory.createInternalParser(in, charset), parserFactory.getValueBufferProvider(), rejectDuplicateKeys); } public JsonReader createReader(final JsonParser parser) { - return new JsonReaderImpl(parser, parserFactory.getValueBufferProvider()); + return new JsonReaderImpl(parser, parserFactory.getValueBufferProvider(), rejectDuplicateKeys); } @Override diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java index 24de161..969ad9a 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java @@ -36,12 +36,14 @@ import static java.util.Collections.emptyMap; public class JsonReaderImpl implements JsonReader { private final JohnzonJsonParser parser; private final BufferStrategy.BufferProvider<char[]> bufferProvider; + private final RejectDuplicateKeysMode rejectDuplicateKeysMode; private boolean closed = false; private boolean subStreamReader; - public JsonReaderImpl(final JsonParser parser, final BufferStrategy.BufferProvider<char[]> bufferProvider) { - this(parser, false, bufferProvider); + public JsonReaderImpl(final JsonParser parser, final BufferStrategy.BufferProvider<char[]> bufferProvider, + final RejectDuplicateKeysMode rejectDuplicateKeysMode) { + this(parser, false, bufferProvider, rejectDuplicateKeysMode); } /** @@ -51,7 +53,8 @@ public class JsonReaderImpl implements JsonReader { * @param bufferProvider buffer provider for toString of created instances. */ public JsonReaderImpl(final JsonParser parser, boolean subStreamReader, - final BufferStrategy.BufferProvider<char[]> bufferProvider) { + final BufferStrategy.BufferProvider<char[]> bufferProvider, + final RejectDuplicateKeysMode rejectDuplicateKeys) { this.bufferProvider = bufferProvider; if (parser instanceof JohnzonJsonParser) { this.parser = (JohnzonJsonParser) parser; @@ -60,6 +63,7 @@ public class JsonReaderImpl implements JsonReader { } this.subStreamReader = subStreamReader; + this.rejectDuplicateKeysMode = rejectDuplicateKeys; } @Override @@ -85,14 +89,14 @@ public class JsonReaderImpl implements JsonReader { switch (next) { case START_OBJECT: - final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl(emptyMap(), bufferProvider); + final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, rejectDuplicateKeysMode); parseObject(objectBuilder); if (!subStreamReader && parser.hasNext()) { throw new JsonParsingException("Expected end of file", parser.getLocation()); } return objectBuilder.build(); case START_ARRAY: - final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl(emptyList(), bufferProvider); + final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl(emptyList(), bufferProvider, rejectDuplicateKeysMode); parseArray(arrayBuilder); if (!subStreamReader && parser.hasNext()) { throw new JsonParsingException("Expected end of file", parser.getLocation()); @@ -178,13 +182,13 @@ public class JsonReaderImpl implements JsonReader { break; case START_OBJECT: - JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider); + JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, rejectDuplicateKeysMode); parseObject(subObject); builder.add(key, subObject); break; case START_ARRAY: - JsonArrayBuilder subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider); + JsonArrayBuilder subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider, rejectDuplicateKeysMode); parseArray(subArray); builder.add(key, subArray); break; @@ -238,14 +242,14 @@ public class JsonReaderImpl implements JsonReader { break; case START_OBJECT: - JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider); + JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, rejectDuplicateKeysMode); parseObject(subObject); builder.add(subObject); break; case START_ARRAY: JsonArrayBuilder subArray = null; - parseArray(subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider)); + parseArray(subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider, rejectDuplicateKeysMode)); builder.add(subArray); break; diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java b/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java new file mode 100644 index 0000000..5e48eea --- /dev/null +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.core; + +import javax.json.JsonException; +import javax.json.JsonValue; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import static java.util.Arrays.asList; + +public enum RejectDuplicateKeysMode { + DEFAULT(Map::put), + TRUE((map, k, v) -> { + if (map.put(k, v) != null) { + throw new JsonException("Rejected key: '" + k + "', already present"); + } + }); + + static final List<String> CONFIG_KEYS = asList( + "johnzon.rejectDuplicateKeys", // our specific one + "org.glassfish.json.rejectDuplicateKeys" // the spec includes it (yes :facepalm:) + ); + + public static RejectDuplicateKeysMode from(final Map<String, ?> config) { + if (config == null) { + return DEFAULT; + } + return CONFIG_KEYS.stream() + .map(config::get) + .filter(Objects::nonNull) + .findFirst() + .map(String::valueOf) + .map(it -> "false".equalsIgnoreCase(it) ? "DEFAULT" : it) // alias to avoid to add an enum value for nothing + .map(it -> valueOf(it.toUpperCase(Locale.ROOT).trim())) + .orElse(DEFAULT); + } + + private final Put put; + + RejectDuplicateKeysMode(final Put put) { + this.put = put; + } + + public Put put() { + return put; + } + + @FunctionalInterface + public interface Put { + void put(final Map<String, JsonValue> receiptor, final String key, final JsonValue value); + } +} diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java index 3cfd22b..be97361 100644 --- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java +++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java @@ -28,6 +28,7 @@ import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import javax.json.Json; +import javax.json.JsonException; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import javax.json.JsonValue; @@ -36,6 +37,14 @@ import org.junit.Assert; import org.junit.Test; public class JsonObjectBuilderImplTest { + @Test(expected = JsonException.class) + public void rejectedKeys() { + Json.createBuilderFactory(singletonMap("johnzon.rejectDuplicateKeys", true)) + .createObjectBuilder() + .add("foo", 1) + .add("foo", 2); + } + @Test public void createObjectBuilderMapSupport() { final Map<String, Object> initial = new HashMap<>(); diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java index becdf5f..5da7fa7 100644 --- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java +++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java @@ -19,6 +19,7 @@ package org.apache.johnzon.core; import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -36,6 +37,7 @@ import java.util.Map; import javax.json.Json; import javax.json.JsonArray; +import javax.json.JsonException; import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonReader; @@ -64,6 +66,14 @@ public class JsonReaderImplTest { return Collections.EMPTY_MAP; } + @Test(expected = JsonException.class) + public void rejectedKeys() { + Json.createReaderFactory(singletonMap("johnzon.rejectDuplicateKeys", true)).createReader(new StringReader("{" + + "\"a\":1," + + "\"a\":2" + + "}")).readObject(); + } + @Test(expected = JsonParsingException.class) public void badTypeObject() { Json.createReaderFactory(getFactoryConfig()).createReader(new StringReader("[]")).readObject();