GSON不区分大小写的枚举反序列化

36

我有一个枚举:

enum Type {
    LIVE, UPCOMING, REPLAY
}

还有一些 JSON:

{
    "type": "live"
}

还有一个类:

class Event {
    Type type;
}

当我尝试使用GSON反序列化JSON时,由于JSON中的类型字段大小写不匹配枚举类型,Event类型字段返回null

Events events = new Gson().fromJson(json, Event.class);
如果我将枚举更改为以下内容,则一切正常:
enum Type {
    live, upcoming, replay
}

然而,我希望将枚举常量保留为全大写。

我认为我需要编写一个适配器,但是没有找到任何好的文档或示例。

什么是最好的解决方案?


编辑:

我已经能够使 JsonDeserializer 正常工作了。不过,是否有更通用的方法来编写它,因为每当枚举值和 JSON 字符串之间存在大小写不匹配时,都需要编写它这样做会很不幸。

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());
    }
}
4个回答

99
我最近发现了一种更简单的方法来做这件事,就是使用@SerializedName注释。我在这里找到了它,在EnumTest.java中的 Gender 类(大约在第195行):

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

这假设你所有的类型都是小写的,而不是"大小写不敏感"的。
public enum Type {
    @SerializedName("live")
    LIVE,

    @SerializedName("upcoming")
    UPCOMING,

    @SerializedName("replay")
    REPLAY;
}

这是我找到的最简单和最通用的方法。希望它能帮到你。


2
如果我能送你更多的积分……或者像啤酒或棒棒糖一样。 - Spedge
这真的很棒。代表整个Android团队:谢谢 :) - balazsbalazs
真希望我之前就知道这个! - Philio
1
是的,看起来很简单,但它并没有避免字符串的冗余重复。我希望Gson能够为枚举类型提供类似于LOWER_CASE_WITH_UNDERSCORES字段命名策略的功能... - friederbluemle
1
它很整洁、清晰且实用,但是它并不真正是“不区分大小写”,对吧?因为它只能解析小写字母的项目,所以如果遇到像“liVE”这样的内容,它也无法解析。 - Alanight

25

现在可以像这样为@SerializedName添加多个值:

public enum Type {
    @SerializedName(value = "live", alternate = {"LIVE"})
    LIVE,

    @SerializedName(value = "upcoming", alternate = {"UPCOMING"})
    UPCOMING,

    @SerializedName(value = "replay", alternate = {"REPLAY"})
    REPLAY;
}

我认为对你来说有点晚了,但我希望它能帮助其他人!


22

这是一个相当古老的问题,但被接受的答案对我没有用,而使用@SerializedName是不够的,因为我想确保可以匹配"value""Value""VALUE"。我成功地根据问题中发布的代码制作了一个通用适配器:

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;
        }
    }
}

并且要使用它:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyEnum.class, new UppercaseEnumAdapter());
Gson gson = gsonBuilder.create();

这就是实现“不区分大小写”的方法。它应该被接受作为答案。 - Alanight

22

方便您的是,这跟TypeAdapterFactory的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);
  }
}

请注意,上述代码将小写的枚举值进行了序列化,并且仅对小写值的完全匹配进行正确的反序列化。要执行不区分大小写的反序列化,请修改read()方法: return lowercaseToConstant.get(toLowercase(reader.nextString()))。要使用原始大小写进行序列化,请修改write()方法: out.value(value.toString()) - nivs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接