36

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());
    }
}
Denys Kurochkin
  • 1,360
  • 1
  • 18
  • 34
Steve
  • 53,375
  • 33
  • 96
  • 141

4 Answers4

99

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):

https://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/EnumTest.java?r=1230

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.

Denys Kurochkin
  • 1,360
  • 1
  • 18
  • 34
prodaea
  • 1,720
  • 1
  • 14
  • 20
  • 2
    If only I could send you more points...or like, a beer or a lollipop. – Spedge Jan 16 '14 at 15:42
  • This is really neat. In the name of a whole android team: thx :) – balazsbalazs Jan 23 '14 at 11:57
  • Wish I had known about this before! – Philio Nov 26 '14 at 09:12
  • 1
    Yeah, it looks simple enough, but it doesn't avoid the redundant duplication of strings. I wish Gson would have something similar to `LOWER_CASE_WITH_UNDERSCORES` field naming policy for enums... – friederbluemle Jul 07 '18 at 07:07
  • 1
    It's neat, clear & useful, however, it's NOT really "Non-Case Sensitive", though, is it? Because it could only parse the items in lower case, so if it encountered something like "liVE", it couldn't parse, either. – Alanight Jan 17 '19 at 02:52
24

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!

Denys Kurochkin
  • 1,360
  • 1
  • 18
  • 34
Blunderer
  • 934
  • 7
  • 15
22

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();
Denys Kurochkin
  • 1,360
  • 1
  • 18
  • 34
jbarguil
  • 254
  • 2
  • 5
22

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);
  }
}
yair
  • 8,945
  • 4
  • 31
  • 50
Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
  • Notice that the above code _serializes_ a lowercase Enum value and _deserializes_ correctly only for exact matches of the lowercase values. To perform case-insensitive deserialization, modify the read() method: `return lowercaseToConstant.get(toLowercase(reader.nextString()))`. And to serialize using the original case, modify the write() method: `out.value(value.toString())`. – nivs Oct 24 '17 at 23:33