Yes
Gson is modular enough so you can use your own TypeAdapterFactory for an enumeration case. Your custom adapter will return your own EnumTypeAdapter and manage the desired case. Let the code say.
package stackoverflow.questions.q16715117; import java.io.IOException; import java.util.*; import com.google.gson.*; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.*; public class Q16715117 { public static void main(String[] args) { GsonBuilder gb = new GsonBuilder(); gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY); Container c1 = new Container(); Gson g = gb.create(); String s1 = "{\"colour\":\"RED\",\"number\":42}"; c1 = g.fromJson(s1, Container.class); System.out.println("Result: "+ c1.toString()); } public static final TypeAdapterFactory CUSTOM_ENUM_FACTORY = newEnumTypeHierarchyFactory(); public static TypeAdapterFactory newEnumTypeHierarchyFactory() { return new TypeAdapterFactory() { @SuppressWarnings({"rawtypes", "unchecked"}) public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { Class<? super T> rawType = typeToken.getRawType(); if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) { return null; } if (!rawType.isEnum()) { rawType = rawType.getSuperclass();
Plus, I declared a custom exception to execute:
public class GsonEnumParsinException extends RuntimeException { String notFoundEnumValue; String enumName; String fieldName; public GsonEnumParsinException(String notFoundEnumValue, String enumName) { this.notFoundEnumValue = notFoundEnumValue; this.enumName = enumName; } @Override public String toString() { return "GsonEnumParsinException [notFoundEnumValue=" + notFoundEnumValue + ", enumName=" + enumName + "]"; } public String getNotFoundEnumValue() { return notFoundEnumValue; } @Override public String getMessage() { return "Cannot found " + notFoundEnumValue + " for enum " + enumName; } }
These are the classes that I used in the example:
public enum Colour { WHITE, YELLOW, BLACK; } public class Container { private Colour colour; private int number; public Colour getColour() { return colour; } public void setColour(Colour colour) { this.colour = colour; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public String toString() { return "Container [colour=" + colour + ", number=" + number + "]"; } }
This gives this stack:
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour] at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:77) at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) at com.google.gson.Gson.fromJson(Gson.java:803) at com.google.gson.Gson.fromJson(Gson.java:768) at com.google.gson.Gson.fromJson(Gson.java:717) at com.google.gson.Gson.fromJson(Gson.java:689) at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:22)
Unfortunately, the EnumTypeAdapter knows nothing about the context it is calling, so this solution is not enough to catch the name of the field.
Edit
So, you need to use another TypeAdapter , which I called CustomReflectiveTypeAdapterFactory , and this is almost a copy of CustomReflectiveTypeAdapterFactory , and I changed this exception a bit, so:
public final class CustomReflectiveTypeAdapterFactory implements TypeAdapterFactory { private final ConstructorConstructor constructorConstructor; private final FieldNamingStrategy fieldNamingPolicy; private final Excluder excluder; public CustomReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder) { this.constructorConstructor = constructorConstructor; this.fieldNamingPolicy = fieldNamingPolicy; this.excluder = excluder; } public boolean excludeField(Field f, boolean serialize) { return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize); } private String getFieldName(Field f) { SerializedName serializedName = f.getAnnotation(SerializedName.class); return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value(); } public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { Class<? super T> raw = type.getRawType(); if (!Object.class.isAssignableFrom(raw)) { return null;
The most important part is the read method, where I catch an exception and add a field name and throw an exception. Note that the CustomTypeAdapterRuntimeTypeWrapper class is simply a renamed copy of TypeAdapterRuntimeTypeWrapper in internal libraries, since the class is private.
So, the main method changes as follows:
Map<Type, InstanceCreator<?>> instanceCreators = new HashMap<Type, InstanceCreator<?>>(); Excluder excluder = Excluder.DEFAULT; FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY; GsonBuilder gb = new GsonBuilder(); gb.registerTypeAdapterFactory(new CustomReflectiveTypeAdapterFactory(new ConstructorConstructor(instanceCreators), fieldNamingPolicy, excluder)); gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY); Gson g = gb.create();
and now you have this stacktrace (the changes to the exception are so simple that I omitted them):
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour, fieldName=colour] at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:90) at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1) at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$1.read(CustomReflectiveTypeAdapterFactory.java:79) at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$Adapter.read(CustomReflectiveTypeAdapterFactory.java:162) at com.google.gson.Gson.fromJson(Gson.java:803) at com.google.gson.Gson.fromJson(Gson.java:768) at com.google.gson.Gson.fromJson(Gson.java:717) at com.google.gson.Gson.fromJson(Gson.java:689) at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:35)
Of course, this solution brings some costs.
- First of all, you need to copy some private / final classes and make your changes. If the library is updated, you need to check your code again (the fork of the source code will be the same, but at least you do not need to copy all this code).
- If you are configuring a field exclusion strategy, constructors, or field naming policies, you must replicate them in the
CustomReflectiveTypeAdapterFactory , since I do not see the ability to pass them from the constructor.