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:
commit
9d7fd891ee
|
@ -110,6 +110,7 @@ public final class Gson {
|
||||||
static final boolean DEFAULT_ESCAPE_HTML = true;
|
static final boolean DEFAULT_ESCAPE_HTML = true;
|
||||||
static final boolean DEFAULT_SERIALIZE_NULLS = false;
|
static final boolean DEFAULT_SERIALIZE_NULLS = false;
|
||||||
static final boolean DEFAULT_COMPLEX_MAP_KEYS = 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_SPECIALIZE_FLOAT_VALUES = false;
|
||||||
static final boolean DEFAULT_USE_JDK_UNSAFE = true;
|
static final boolean DEFAULT_USE_JDK_UNSAFE = true;
|
||||||
static final String DEFAULT_DATE_PATTERN = null;
|
static final String DEFAULT_DATE_PATTERN = null;
|
||||||
|
@ -142,6 +143,7 @@ public final class Gson {
|
||||||
final Map<Type, InstanceCreator<?>> instanceCreators;
|
final Map<Type, InstanceCreator<?>> instanceCreators;
|
||||||
final boolean serializeNulls;
|
final boolean serializeNulls;
|
||||||
final boolean complexMapKeySerialization;
|
final boolean complexMapKeySerialization;
|
||||||
|
final boolean duplicateMapKeyDeserialization;
|
||||||
final boolean generateNonExecutableJson;
|
final boolean generateNonExecutableJson;
|
||||||
final boolean htmlSafe;
|
final boolean htmlSafe;
|
||||||
final boolean prettyPrinting;
|
final boolean prettyPrinting;
|
||||||
|
@ -195,7 +197,7 @@ public final class Gson {
|
||||||
public Gson() {
|
public Gson() {
|
||||||
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
|
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
|
||||||
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
|
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_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
|
||||||
DEFAULT_USE_JDK_UNSAFE,
|
DEFAULT_USE_JDK_UNSAFE,
|
||||||
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
|
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
|
||||||
|
@ -206,7 +208,7 @@ public final class Gson {
|
||||||
|
|
||||||
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
|
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
|
||||||
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
|
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 prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
|
||||||
boolean useJdkUnsafe,
|
boolean useJdkUnsafe,
|
||||||
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
|
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
|
||||||
|
@ -221,6 +223,7 @@ public final class Gson {
|
||||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters);
|
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters);
|
||||||
this.serializeNulls = serializeNulls;
|
this.serializeNulls = serializeNulls;
|
||||||
this.complexMapKeySerialization = complexMapKeySerialization;
|
this.complexMapKeySerialization = complexMapKeySerialization;
|
||||||
|
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
|
||||||
this.generateNonExecutableJson = generateNonExecutableGson;
|
this.generateNonExecutableJson = generateNonExecutableGson;
|
||||||
this.htmlSafe = htmlSafe;
|
this.htmlSafe = htmlSafe;
|
||||||
this.prettyPrinting = prettyPrinting;
|
this.prettyPrinting = prettyPrinting;
|
||||||
|
@ -295,7 +298,7 @@ public final class Gson {
|
||||||
|
|
||||||
// type adapters for composite and user-defined types
|
// type adapters for composite and user-defined types
|
||||||
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
|
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
|
||||||
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
|
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization, duplicateMapKeyDeserialization));
|
||||||
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
|
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
|
||||||
factories.add(jsonAdapterFactory);
|
factories.add(jsonAdapterFactory);
|
||||||
factories.add(TypeAdapters.ENUM_FACTORY);
|
factories.add(TypeAdapters.ENUM_FACTORY);
|
||||||
|
|
|
@ -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_COMPLEX_MAP_KEYS;
|
||||||
import static com.google.gson.Gson.DEFAULT_DATE_PATTERN;
|
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_ESCAPE_HTML;
|
||||||
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
|
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
|
||||||
import static com.google.gson.Gson.DEFAULT_LENIENT;
|
import static com.google.gson.Gson.DEFAULT_LENIENT;
|
||||||
|
@ -93,6 +94,7 @@ public final class GsonBuilder {
|
||||||
private int dateStyle = DateFormat.DEFAULT;
|
private int dateStyle = DateFormat.DEFAULT;
|
||||||
private int timeStyle = DateFormat.DEFAULT;
|
private int timeStyle = DateFormat.DEFAULT;
|
||||||
private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS;
|
private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS;
|
||||||
|
private boolean duplicateMapKeyDeserialization = DEFAULT_DUPLICATE_MAP_KEYS;
|
||||||
private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES;
|
private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES;
|
||||||
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
|
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
|
||||||
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
|
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
|
||||||
|
@ -124,6 +126,7 @@ public final class GsonBuilder {
|
||||||
this.instanceCreators.putAll(gson.instanceCreators);
|
this.instanceCreators.putAll(gson.instanceCreators);
|
||||||
this.serializeNulls = gson.serializeNulls;
|
this.serializeNulls = gson.serializeNulls;
|
||||||
this.complexMapKeySerialization = gson.complexMapKeySerialization;
|
this.complexMapKeySerialization = gson.complexMapKeySerialization;
|
||||||
|
this.duplicateMapKeyDeserialization = gson.duplicateMapKeyDeserialization;
|
||||||
this.generateNonExecutableJson = gson.generateNonExecutableJson;
|
this.generateNonExecutableJson = gson.generateNonExecutableJson;
|
||||||
this.escapeHtmlChars = gson.htmlSafe;
|
this.escapeHtmlChars = gson.htmlSafe;
|
||||||
this.prettyPrinting = gson.prettyPrinting;
|
this.prettyPrinting = gson.prettyPrinting;
|
||||||
|
@ -288,6 +291,22 @@ public final class GsonBuilder {
|
||||||
return this;
|
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.
|
* Configures Gson to exclude inner classes during serialization.
|
||||||
*
|
*
|
||||||
|
@ -674,7 +693,7 @@ public final class GsonBuilder {
|
||||||
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
||||||
|
|
||||||
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
|
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
|
||||||
serializeNulls, complexMapKeySerialization,
|
serializeNulls, complexMapKeySerialization, duplicateMapKeyDeserialization,
|
||||||
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
|
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
|
||||||
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
|
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
|
||||||
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
|
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
|
||||||
|
|
|
@ -105,11 +105,13 @@ import java.util.Map;
|
||||||
public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final ConstructorConstructor constructorConstructor;
|
private final ConstructorConstructor constructorConstructor;
|
||||||
final boolean complexMapKeySerialization;
|
final boolean complexMapKeySerialization;
|
||||||
|
final boolean duplicateMapKeyDeserialization;
|
||||||
|
|
||||||
public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
|
public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
|
||||||
boolean complexMapKeySerialization) {
|
boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization) {
|
||||||
this.constructorConstructor = constructorConstructor;
|
this.constructorConstructor = constructorConstructor;
|
||||||
this.complexMapKeySerialization = complexMapKeySerialization;
|
this.complexMapKeySerialization = complexMapKeySerialization;
|
||||||
|
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
@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);
|
K key = keyTypeAdapter.read(in);
|
||||||
V value = valueTypeAdapter.read(in);
|
V value = valueTypeAdapter.read(in);
|
||||||
V replaced = map.put(key, value);
|
V replaced = map.put(key, value);
|
||||||
if (replaced != null) {
|
if (!duplicateMapKeyDeserialization && replaced != null) {
|
||||||
throw new JsonSyntaxException("duplicate key: " + key);
|
throw new JsonSyntaxException("duplicate key: " + key);
|
||||||
}
|
}
|
||||||
in.endArray();
|
in.endArray();
|
||||||
|
@ -185,7 +187,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
K key = keyTypeAdapter.read(in);
|
K key = keyTypeAdapter.read(in);
|
||||||
V value = valueTypeAdapter.read(in);
|
V value = valueTypeAdapter.read(in);
|
||||||
V replaced = map.put(key, value);
|
V replaced = map.put(key, value);
|
||||||
if (replaced != null) {
|
if (!duplicateMapKeyDeserialization && replaced != null) {
|
||||||
throw new JsonSyntaxException("duplicate key: " + key);
|
throw new JsonSyntaxException("duplicate key: " + key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public final class GsonTest extends TestCase {
|
||||||
|
|
||||||
public void testOverridesDefaultExcluder() {
|
public void testOverridesDefaultExcluder() {
|
||||||
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
|
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,
|
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||||
|
@ -68,7 +68,7 @@ public final class GsonTest extends TestCase {
|
||||||
|
|
||||||
public void testClonedTypeAdapterFactoryListsAreIndependent() {
|
public void testClonedTypeAdapterFactoryListsAreIndependent() {
|
||||||
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
|
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,
|
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||||
|
|
|
@ -78,6 +78,17 @@ public class MapTest extends TestCase {
|
||||||
assertEquals(2, target.get("b").intValue());
|
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"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
public void testRawMapSerialization() {
|
public void testRawMapSerialization() {
|
||||||
Map map = new LinkedHashMap();
|
Map map = new LinkedHashMap();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user