Skip to content

Commit

Permalink
Improve exception message for duplicate field names (google#2251)
Browse files Browse the repository at this point in the history
(cherry picked from commit 6c27553)
  • Loading branch information
Marcono1234 authored and tibor-universe committed Jan 18, 2023
1 parent 41b8ba9 commit 8930af3
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 12 deletions.
Expand Up @@ -127,7 +127,7 @@ private static <M extends AccessibleObject & Member> void checkAccessible(Object
}
}

private ReflectiveTypeAdapterFactory.BoundField createBoundField(
private BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
final boolean blockInaccessible) {
Expand All @@ -149,7 +149,7 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(

@SuppressWarnings("unchecked")
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
return new ReflectiveTypeAdapterFactory.BoundField(name, field.getName(), serialize, deserialize) {
return new BoundField(name, field, serialize, deserialize) {
@Override void write(JsonWriter writer, Object source)
throws IOException, IllegalAccessException {
if (!serialized) return;
Expand Down Expand Up @@ -195,7 +195,6 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
return result;
}

Type declaredType = type.getType();
Class<?> originalRaw = raw;
while (raw != Object.class) {
Field[] fields = raw.getDeclaredFields();
Expand Down Expand Up @@ -234,8 +233,9 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
if (previous == null) previous = replaced;
}
if (previous != null) {
throw new IllegalArgumentException(declaredType
+ " declares multiple JSON fields named " + previous.name);
throw new IllegalArgumentException("Class " + originalRaw.getName()
+ " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field));
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
Expand All @@ -246,14 +246,16 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,

static abstract class BoundField {
final String name;
final Field field;
/** Name of the underlying field */
final String fieldName;
final boolean serialized;
final boolean deserialized;

protected BoundField(String name, String fieldName, boolean serialized, boolean deserialized) {
protected BoundField(String name, Field field, boolean serialized, boolean deserialized) {
this.name = name;
this.fieldName = fieldName;
this.field = field;
this.fieldName = field.getName();
this.serialized = serialized;
this.deserialized = deserialized;
}
Expand Down
Expand Up @@ -75,8 +75,7 @@ public static String getAccessibleObjectDescription(AccessibleObject object, boo
String description;

if (object instanceof Field) {
Field field = (Field) object;
description = "field '" + field.getDeclaringClass().getName() + "#" + field.getName() + "'";
description = "field '" + fieldToString((Field) object) + "'";
} else if (object instanceof Method) {
Method method = (Method) object;

Expand All @@ -97,6 +96,14 @@ public static String getAccessibleObjectDescription(AccessibleObject object, boo
return description;
}

/**
* Creates a string representation for a field, omitting modifiers and
* the field type.
*/
public static String fieldToString(Field field) {
return field.getDeclaringClass().getName() + "#" + field.getName();
}

/**
* Creates a string representation for a constructor.
* E.g.: {@code java.lang.String(char[], int, int)}
Expand Down
Expand Up @@ -22,10 +22,8 @@
import com.google.gson.annotations.SerializedName;
import com.google.gson.common.TestTypes.ClassWithSerializedNameFields;
import com.google.gson.common.TestTypes.StringWrapper;

import junit.framework.TestCase;

import java.lang.reflect.Field;
import junit.framework.TestCase;

/**
* Functional tests for naming policies.
Expand Down Expand Up @@ -122,6 +120,12 @@ public void testGsonDuplicateNameUsingSerializedNameFieldNamingPolicySerializati
gson.toJson(target);
fail();
} catch (IllegalArgumentException expected) {
assertEquals(
"Class com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields declares multiple JSON fields named 'a';"
+ " conflict is caused by fields com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#a and"
+ " com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#b",
expected.getMessage()
);
}
}

Expand Down
25 changes: 25 additions & 0 deletions gson/src/test/java/com/google/gson/functional/ObjectTest.java
Expand Up @@ -145,6 +145,31 @@ public void testClassWithNoFieldsDeserialization() throws Exception {
assertEquals(expected, target);
}

private static class Subclass extends Superclass1 {
}
private static class Superclass1 extends Superclass2 {
@SuppressWarnings("unused")
String s;
}
private static class Superclass2 {
@SuppressWarnings("unused")
String s;
}

public void testClassWithDuplicateFields() {
try {
gson.getAdapter(Subclass.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals(
"Class com.google.gson.functional.ObjectTest$Subclass declares multiple JSON fields named 's';"
+ " conflict is caused by fields com.google.gson.functional.ObjectTest$Superclass1#s and"
+ " com.google.gson.functional.ObjectTest$Superclass2#s",
e.getMessage()
);
}
}

public void testNestedSerialization() throws Exception {
Nested target = new Nested(new BagOfPrimitives(10, 20, false, "stringValue"),
new BagOfPrimitives(30, 40, true, "stringValue"));
Expand Down

0 comments on commit 8930af3

Please sign in to comment.