Skip to content

Commit f4d371d

Browse files
Fix duplicate key detection when first value is null (#3006)
* Fix duplicate key detection when first value is null MapTypeAdapterFactory detected duplicate map keys by checking the return value of Map.put(): `if (replaced != null)`. This missed duplicates where the first value was null, because Map.put() returns null (the previous value) which passed the != null check. For example, {"a":null,"a":1} was silently accepted as {a=1} instead of throwing JsonSyntaxException("duplicate key: a"). Fix: check Map.containsKey() before put() instead of relying on the return value of put(). This correctly detects duplicates regardless of whether the first value is null. Fixes both the object form and the array-of-entries form (enableComplexMapKeySerialization). * Apply spotless formatting
1 parent 27d9ba1 commit f4d371d

3 files changed

Lines changed: 33 additions & 4 deletions

File tree

gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ public Map<K, V> read(JsonReader in) throws IOException {
188188
in.beginArray(); // entry array
189189
K key = keyTypeAdapter.read(in);
190190
V value = valueTypeAdapter.read(in);
191-
V replaced = map.put(key, value);
192-
if (replaced != null) {
191+
if (map.containsKey(key)) {
193192
throw new JsonSyntaxException("duplicate key: " + key);
194193
}
194+
map.put(key, value);
195195
in.endArray();
196196
}
197197
in.endArray();
@@ -201,10 +201,10 @@ public Map<K, V> read(JsonReader in) throws IOException {
201201
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
202202
K key = keyTypeAdapter.read(in);
203203
V value = valueTypeAdapter.read(in);
204-
V replaced = map.put(key, value);
205-
if (replaced != null) {
204+
if (map.containsKey(key)) {
206205
throw new JsonSyntaxException("duplicate key: " + key);
207206
}
207+
map.put(key, value);
208208
}
209209
in.endObject();
210210
}

gson/src/test/java/com/google/gson/functional/MapAsArrayTypeAdapterTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ public void testTwoTypesCollapseToOneDeserialize() {
8080
assertThat(e).hasMessageThat().isEqualTo("duplicate key: 1.0");
8181
}
8282

83+
@Test
84+
public void testDuplicateKeyWithNullFirstValueArrayForm() {
85+
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
86+
87+
String s = "[[\"a\",null],[\"a\",\"x\"]]";
88+
Type type = new TypeToken<Map<String, String>>() {}.getType();
89+
var e = assertThrows(JsonSyntaxException.class, () -> gson.fromJson(s, type));
90+
assertThat(e).hasMessageThat().isEqualTo("duplicate key: a");
91+
}
92+
8393
@Test
8494
public void testMultipleEnableComplexKeyRegistrationHasNoEffect() {
8595
Type type = new TypeToken<Map<Point, String>>() {}.getType();

gson/src/test/java/com/google/gson/functional/MapTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,25 @@ public void testMapDeserializationWithDuplicateKeys() {
693693
assertThat(e).hasMessageThat().isEqualTo("duplicate key: a");
694694
}
695695

696+
@Test
697+
public void testMapDeserializationWithDuplicateKeysNullFirstValue() {
698+
Type type = new TypeToken<Map<String, Integer>>() {}.getType();
699+
// Duplicate key where the first value is null; map.put() returns null (the previous value)
700+
// so the old `if (replaced != null)` check failed to detect the duplicate
701+
var e =
702+
assertThrows(JsonSyntaxException.class, () -> gson.fromJson("{\"a\":null,\"a\":1}", type));
703+
assertThat(e).hasMessageThat().isEqualTo("duplicate key: a");
704+
}
705+
706+
@Test
707+
public void testMapDeserializationWithDuplicateKeysBothNull() {
708+
Type type = new TypeToken<Map<String, Integer>>() {}.getType();
709+
var e =
710+
assertThrows(
711+
JsonSyntaxException.class, () -> gson.fromJson("{\"a\":null,\"a\":null}", type));
712+
assertThat(e).hasMessageThat().isEqualTo("duplicate key: a");
713+
}
714+
696715
@Test
697716
public void testSerializeMapOfMaps() {
698717
Type type = new TypeToken<Map<String, Map<String, String>>>() {}.getType();

0 commit comments

Comments
 (0)