GSON Case-Insensitive Enum Deserialization
-
04-12-2019 - |
Question
I have an enum:
enum Type {
LIVE, UPCOMING, REPLAY
}
And some JSON:
{
"type": "live"
}
And a class:
class Event {
Type type;
}
When I try to deserialize the JSON, using GSON, I receive null
for the Event
type field, since the case of the type field in the JSON does not match that of the enum.
Events events = new Gson().fromJson(json, Event.class);
If I change the enum to the following, then all works fine:
enum Type {
live, upcoming, replay
}
However, I would like to leave the enum constants as all uppercase.
I'm assuming I need to write an adapter but haven't found any good documentation or examples.
What is the best solution?
Edit:
I was able to get a JsonDeserializer working. Is there a more generic way to write this though, as it would be unfortunate to have to write this each time there is a case mismatch between enum values and JSON strings.
protected static class TypeCaseInsensitiveEnumAdapter implements JsonDeserializer<Type> {
@Override
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
return Type.valueOf(json.getAsString().toUpperCase());
}
}
Solution
Conveniently for you, this is very close to the example given in TypeAdapterFactory's Javadoc:
public class CaseInsensitiveEnumTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (!rawType.isEnum()) {
return null;
}
final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
for (T constant : rawType.getEnumConstants()) {
lowercaseToConstant.put(toLowercase(constant), constant);
}
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(toLowercase(value));
}
}
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
} else {
return lowercaseToConstant.get(toLowercase(reader.nextString()));
}
}
};
}
private String toLowercase(Object o) {
return o.toString().toLowerCase(Locale.US);
}
}
OTHER TIPS
A simpler way I found (just now) to do this is to use the @SerializedName
annotation. I found it in the EnumTest.java
here (the Gender
class around ln 195):
This assumes that all of your Types will come in as lowercase as opposed to being "case insensitive"
public enum Type {
@SerializedName("live")
LIVE,
@SerializedName("upcoming")
UPCOMING,
@SerializedName("replay")
REPLAY;
}
This was the simplest and most generic way I found to do this. Hope it helps you.
This is a rather old question, but the accepted answer didn't work for me, and using @SerializedName
is not enough because I want to make sure I can match "value"
, "Value"
and "VALUE"
.
I managed to make a generic Adapter based on the code posted in the question:
public class UppercaseEnumAdapter implements JsonDeserializer<Enum> {
@Override
public Enum deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context)
throws JsonParseException {
try {
if(type instanceof Class && ((Class<?>) type).isEnum())
return Enum.valueOf((Class<Enum>) type, json.getAsString().toUpperCase());
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
And to use it:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyEnum.class, new UppercaseEnumAdapter());
Gson gson = gsonBuilder.create();
Now you can add multiple values for @SerializedName
like this:
public enum Type {
@SerializedName(value = "live", alternate = {"LIVE"})
LIVE,
@SerializedName(value = "upcoming", alternate = {"UPCOMING"})
UPCOMING,
@SerializedName(value = "replay", alternate = {"REPLAY"})
REPLAY;
}
I think it's a bit late for you but I hope it will help anyone else!