Skip to content

Commit 4b2ee88

Browse files
committed
Improve exception message for duplicate field names
1 parent bc04dd2 commit 4b2ee88

4 files changed

Lines changed: 50 additions & 12 deletions

File tree

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private static <M extends AccessibleObject & Member> void checkAccessible(Object
139139
}
140140
}
141141

142-
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
142+
private BoundField createBoundField(
143143
final Gson context, final Field field, final Method accessor, final String name,
144144
final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
145145
final boolean blockInaccessible) {
@@ -161,7 +161,7 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
161161

162162
@SuppressWarnings("unchecked")
163163
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
164-
return new ReflectiveTypeAdapterFactory.BoundField(name, field.getName(), serialize, deserialize) {
164+
return new BoundField(name, field, serialize, deserialize) {
165165
@Override void write(JsonWriter writer, Object source)
166166
throws IOException, IllegalAccessException {
167167
if (!serialized) return;
@@ -232,7 +232,6 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
232232
return result;
233233
}
234234

235-
Type declaredType = type.getType();
236235
Class<?> originalRaw = raw;
237236
while (raw != Object.class) {
238237
Field[] fields = raw.getDeclaredFields();
@@ -298,8 +297,9 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
298297
if (previous == null) previous = replaced;
299298
}
300299
if (previous != null) {
301-
throw new IllegalArgumentException(declaredType
302-
+ " declares multiple JSON fields named " + previous.name);
300+
throw new IllegalArgumentException("Class " + originalRaw.getName()
301+
+ " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
302+
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field));
303303
}
304304
}
305305
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
@@ -310,14 +310,16 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
310310

311311
static abstract class BoundField {
312312
final String name;
313+
final Field field;
313314
/** Name of the underlying field */
314315
final String fieldName;
315316
final boolean serialized;
316317
final boolean deserialized;
317318

318-
protected BoundField(String name, String fieldName, boolean serialized, boolean deserialized) {
319+
protected BoundField(String name, Field field, boolean serialized, boolean deserialized) {
319320
this.name = name;
320-
this.fieldName = fieldName;
321+
this.field = field;
322+
this.fieldName = field.getName();
321323
this.serialized = serialized;
322324
this.deserialized = deserialized;
323325
}

gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ public static String getAccessibleObjectDescription(AccessibleObject object, boo
5353
String description;
5454

5555
if (object instanceof Field) {
56-
Field field = (Field) object;
57-
description = "field '" + field.getDeclaringClass().getName() + "#" + field.getName() + "'";
56+
description = "field '" + fieldToString((Field) object) + "'";
5857
} else if (object instanceof Method) {
5958
Method method = (Method) object;
6059

@@ -75,6 +74,14 @@ public static String getAccessibleObjectDescription(AccessibleObject object, boo
7574
return description;
7675
}
7776

77+
/**
78+
* Creates a string representation for a field, omitting modifiers and
79+
* the field type.
80+
*/
81+
public static String fieldToString(Field field) {
82+
return field.getDeclaringClass().getName() + "#" + field.getName();
83+
}
84+
7885
/**
7986
* Creates a string representation for a constructor.
8087
* E.g.: {@code java.lang.String(char[], int, int)}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222
import com.google.gson.annotations.SerializedName;
2323
import com.google.gson.common.TestTypes.ClassWithSerializedNameFields;
2424
import com.google.gson.common.TestTypes.StringWrapper;
25-
26-
import junit.framework.TestCase;
27-
2825
import java.lang.reflect.Field;
26+
import junit.framework.TestCase;
2927

3028
/**
3129
* Functional tests for naming policies.
@@ -122,6 +120,12 @@ public void testGsonDuplicateNameUsingSerializedNameFieldNamingPolicySerializati
122120
gson.toJson(target);
123121
fail();
124122
} catch (IllegalArgumentException expected) {
123+
assertEquals(
124+
"Class com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields declares multiple JSON fields named 'a';"
125+
+ " conflict is caused by fields com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#a and"
126+
+ " com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#b",
127+
expected.getMessage()
128+
);
125129
}
126130
}
127131

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,31 @@ public void testClassWithNoFieldsDeserialization() throws Exception {
145145
assertEquals(expected, target);
146146
}
147147

148+
private static class Subclass extends Superclass1 {
149+
}
150+
private static class Superclass1 extends Superclass2 {
151+
@SuppressWarnings("unused")
152+
String s;
153+
}
154+
private static class Superclass2 {
155+
@SuppressWarnings("unused")
156+
String s;
157+
}
158+
159+
public void testClassWithDuplicateFields() {
160+
try {
161+
gson.getAdapter(Subclass.class);
162+
fail();
163+
} catch (IllegalArgumentException e) {
164+
assertEquals(
165+
"Class com.google.gson.functional.ObjectTest$Subclass declares multiple JSON fields named 's';"
166+
+ " conflict is caused by fields com.google.gson.functional.ObjectTest$Superclass1#s and"
167+
+ " com.google.gson.functional.ObjectTest$Superclass2#s",
168+
e.getMessage()
169+
);
170+
}
171+
}
172+
148173
public void testNestedSerialization() throws Exception {
149174
Nested target = new Nested(new BagOfPrimitives(10, 20, false, "stringValue"),
150175
new BagOfPrimitives(30, 40, true, "stringValue"));

0 commit comments

Comments
 (0)