Merge branch 'allow_duplicate_map_key'

# Conflicts:
#	gson/src/main/java/com/google/gson/GsonBuilder.java
#	gson/src/test/java/com/google/gson/GsonTest.java
This commit is contained in:
Johannes Frohnmeyer 2022-05-12 22:54:17 +02:00
commit 9d7fd891ee
Signed by: Johannes
GPG Key ID: E76429612C2929F4
5 changed files with 44 additions and 9 deletions

View File

@ -110,6 +110,7 @@ public final class Gson {
static final boolean DEFAULT_ESCAPE_HTML = true;
static final boolean DEFAULT_SERIALIZE_NULLS = false;
static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;
static final boolean DEFAULT_DUPLICATE_MAP_KEYS = false;
static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false;
static final boolean DEFAULT_USE_JDK_UNSAFE = true;
static final String DEFAULT_DATE_PATTERN = null;
@ -142,6 +143,7 @@ public final class Gson {
final Map<Type, InstanceCreator<?>> instanceCreators;
final boolean serializeNulls;
final boolean complexMapKeySerialization;
final boolean duplicateMapKeyDeserialization;
final boolean generateNonExecutableJson;
final boolean htmlSafe;
final boolean prettyPrinting;
@ -195,7 +197,7 @@ public final class Gson {
public Gson() {
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_DUPLICATE_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
DEFAULT_USE_JDK_UNSAFE,
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
@ -206,7 +208,7 @@ public final class Gson {
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
boolean useJdkUnsafe,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
@ -221,6 +223,7 @@ public final class Gson {
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters);
this.serializeNulls = serializeNulls;
this.complexMapKeySerialization = complexMapKeySerialization;
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting;
@ -295,7 +298,7 @@ public final class Gson {
// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization, duplicateMapKeyDeserialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);

View File

@ -37,6 +37,7 @@ import com.google.gson.stream.JsonReader;
import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS;
import static com.google.gson.Gson.DEFAULT_DATE_PATTERN;
import static com.google.gson.Gson.DEFAULT_DUPLICATE_MAP_KEYS;
import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
import static com.google.gson.Gson.DEFAULT_LENIENT;
@ -93,6 +94,7 @@ public final class GsonBuilder {
private int dateStyle = DateFormat.DEFAULT;
private int timeStyle = DateFormat.DEFAULT;
private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS;
private boolean duplicateMapKeyDeserialization = DEFAULT_DUPLICATE_MAP_KEYS;
private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES;
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
@ -124,6 +126,7 @@ public final class GsonBuilder {
this.instanceCreators.putAll(gson.instanceCreators);
this.serializeNulls = gson.serializeNulls;
this.complexMapKeySerialization = gson.complexMapKeySerialization;
this.duplicateMapKeyDeserialization = gson.duplicateMapKeyDeserialization;
this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe;
this.prettyPrinting = gson.prettyPrinting;
@ -288,6 +291,22 @@ public final class GsonBuilder {
return this;
}
/**
* Configures Gson to deserialize duplicate map keys. Only the value of last entry with the same key will be used, previous values
* will be discarded. By default, Gson throws a {@link JsonSyntaxException} when a key occurs more than once.
*
* <p>Note that enabling support for duplicate maps keys is discouraged because it can make an application less secure.
* When an application interacts with other components using different JSON libraries, they might treat duplicate keys
* differently, allowing an attacker to circumvent security checks.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 2.8
*/
public GsonBuilder enableDuplicateMapKeyDeserialization() {
duplicateMapKeyDeserialization = true;
return this;
}
/**
* Configures Gson to exclude inner classes during serialization.
*
@ -674,7 +693,7 @@ public final class GsonBuilder {
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization,
serializeNulls, complexMapKeySerialization, duplicateMapKeyDeserialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),

View File

@ -105,11 +105,13 @@ import java.util.Map;
public final class MapTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
final boolean complexMapKeySerialization;
final boolean duplicateMapKeyDeserialization;
public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
boolean complexMapKeySerialization) {
boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization) {
this.constructorConstructor = constructorConstructor;
this.complexMapKeySerialization = complexMapKeySerialization;
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
}
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
@ -172,7 +174,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
K key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
if (!duplicateMapKeyDeserialization && replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
in.endArray();
@ -185,7 +187,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
K key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
if (!duplicateMapKeyDeserialization && replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}

View File

@ -53,7 +53,7 @@ public final class GsonTest extends TestCase {
public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
new HashMap<Type, InstanceCreator<?>>(), true, false, false, true, false,
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -68,7 +68,7 @@ public final class GsonTest extends TestCase {
public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
new HashMap<Type, InstanceCreator<?>>(), true, false, false, true, false,
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),

View File

@ -78,6 +78,17 @@ public class MapTest extends TestCase {
assertEquals(2, target.get("b").intValue());
}
public void testMapDuplicateKeyDeserialization() {
Gson gsonWithDuplicateKeys = new GsonBuilder()
.enableDuplicateMapKeyDeserialization()
.create();
Type typeOfMap = new TypeToken<Map<String,Integer>>(){}.getType();
String json = "{\"a\":1,\"b\":2,\"b\":3}";
Map<String,Integer> target = gsonWithDuplicateKeys.fromJson(json, typeOfMap);
assertEquals(1, target.get("a").intValue());
assertEquals(3, target.get("b").intValue());
}
@SuppressWarnings({"unchecked", "rawtypes"})
public void testRawMapSerialization() {
Map map = new LinkedHashMap();