在使用 GSON 解析 JSON 时使用枚举

Posted

技术标签:

【中文标题】在使用 GSON 解析 JSON 时使用枚举【英文标题】:Using Enums while parsing JSON with GSON 【发布时间】:2012-01-02 22:07:38 【问题描述】:

这与我之前在这里提出的一个问题有关

JSON parsing using Gson

我正在尝试解析相同的 JSON,但现在我稍微更改了我的类。


    "lower": 20,
    "upper": 40,
    "delimiter": " ",
    "scope": ["$title"]

我的班级现在看起来像:

public class TruncateElement 

   private int lower;
   private int upper;
   private String delimiter;
   private List<AttributeScope> scope;

   // getters and setters



public enum AttributeScope 

    TITLE("$title"),
    DESCRIPTION("$description"),

    private String scope;

    AttributeScope(String scope) 
        this.scope = scope;
    

    public String getScope() 
        return this.scope;
    

这段代码抛出异常,

com.google.gson.JsonParseException: The JsonDeserializer EnumTypeAdapter failed to deserialized json object "$title" given the type class com.amazon.seo.attribute.template.parse.data.AttributeScope
at 

这个例外是可以理解的,因为根据我之前的问题的解决方案,GSON 期望 Enum 对象实际上被创建为

$title("$title"),
$description("$description");

但是由于这在语法上是不可能的,推荐的解决方案和变通方法是什么?

【问题讨论】:

【参考方案1】:

我想扩展一点 NAZIK/user2724653 答案(就我而言)。这是一段Java代码:

public class Item 
    @SerializedName("status")
    private Status currentState = null;

    // other fields, getters, setters, constructor and other code...

    public enum Status 
        @SerializedName("0")
        BUY,
        @SerializedName("1")
        DOWNLOAD,
        @SerializedName("2")
        DOWNLOADING,
        @SerializedName("3")
        OPEN
     

在 json 文件中,您只有一个字段 "status": "N",,其中 N=0,1,2,3 - 取决于状态值。这就是全部,GSON 与嵌套的 enum 类的值一起工作得很好。就我而言,我从json 数组中解析了Items 的列表:

List<Item> items = new Gson().<List<Item>>fromJson(json,
                                          new TypeToken<List<Item>>().getType());

【讨论】:

这个答案完美解决了一切,不需要类型适配器! 当我使用 Retrofit/Gson 执行此操作时,枚举值的 SerializedName 添加了额外的引号。例如,服务器实际上接收的是"1",而不是简单的1... 如果状态为 5 的 json 到达会发生什么?有没有办法定义默认值? @DmitryBorodin 如果来自 JSON 的值与任何 SerializedName 不匹配,则枚举将默认为 null。未知状态的默认行为可以在包装类中处理。但是,如果您需要 null 以外的“未知”表示,则需要编写自定义反序列化器或类型适配器。【参考方案2】:

来自the documentation for Gson:

Gson 为 Enums 提供默认的序列化和反序列化...如果您希望更改默认表示,可以通过 GsonBuilder.registerTypeAdapter(Type, Object) 注册类型适配器来实现。

以下是这样一种方法。

import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

public class GsonFoo

  public static void main(String[] args) throws Exception
  
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(AttributeScope.class, new AttributeScopeDeserializer());
    Gson gson = gsonBuilder.create();

    TruncateElement element = gson.fromJson(new FileReader("input.json"), TruncateElement.class);

    System.out.println(element.lower);
    System.out.println(element.upper);
    System.out.println(element.delimiter);
    System.out.println(element.scope.get(0));
  


class AttributeScopeDeserializer implements JsonDeserializer<AttributeScope>

  @Override
  public AttributeScope deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  
    AttributeScope[] scopes = AttributeScope.values();
    for (AttributeScope scope : scopes)
    
      if (scope.scope.equals(json.getAsString()))
        return scope;
    
    return null;
  


class TruncateElement

  int lower;
  int upper;
  String delimiter;
  List<AttributeScope> scope;


enum AttributeScope

  TITLE("$title"), DESCRIPTION("$description");

  String scope;

  AttributeScope(String scope)
  
    this.scope = scope;
  

【讨论】:

【参考方案3】:

使用注解@SerializedName:

@SerializedName("$title")
TITLE,
@SerializedName("$description")
DESCRIPTION

【讨论】:

【参考方案4】:

以下 sn-p 消除了对显式 Gson.registerTypeAdapter(...) 的需要,使用自 Gson 2.3 起可用的 @JsonAdapter(class) 注释(参见注释 pm_labs)。

@JsonAdapter(Level.Serializer.class)
public enum Level 
    WTF(0),
    ERROR(1),
    WARNING(2),
    INFO(3),
    DEBUG(4),
    VERBOSE(5);

    int levelCode;

    Level(int levelCode) 
        this.levelCode = levelCode;
    

    static Level getLevelByCode(int levelCode) 
        for (Level level : values())
            if (level.levelCode == levelCode) return level;
        return INFO;
    

    static class Serializer implements JsonSerializer<Level>, JsonDeserializer<Level> 
        @Override
        public JsonElement serialize(Level src, Type typeOfSrc, JsonSerializationContext context) 
            return context.serialize(src.levelCode);
        

        @Override
        public Level deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 
            try 
                return getLevelByCode(json.getAsNumber().intValue());
             catch (JsonParseException e) 
                return INFO;
            
        
    

【讨论】:

请注意,此注解仅从 2.3 版开始可用:google.github.io/gson/apidocs/index.html?com/google/gson/… 小心将您的序列化器/反序列化器类添加到您的 proguard 配置中,因为它们可能会被删除(这发生在我身上)【参考方案5】:

使用 GSON 2.2.2 版,枚举将被轻松编组和解组。

import com.google.gson.annotations.SerializedName;

enum AttributeScope

  @SerializedName("$title")
  TITLE("$title"),

  @SerializedName("$description")
  DESCRIPTION("$description");

  private String scope;

  AttributeScope(String scope)
  
    this.scope = scope;
  

  public String getScope() 
    return scope;
  

【讨论】:

【参考方案6】:

如果你真的想使用 Enum 的序数值,你可以注册一个类型适配器工厂来覆盖 Gson 的默认工厂。

public class EnumTypeAdapter <T extends Enum<T>> extends TypeAdapter<T> 
    private final Map<Integer, T> nameToConstant = new HashMap<>();
    private final Map<T, Integer> constantToName = new HashMap<>();

    public EnumTypeAdapter(Class<T> classOfT) 
        for (T constant : classOfT.getEnumConstants()) 
            Integer name = constant.ordinal();
            nameToConstant.put(name, constant);
            constantToName.put(constant, name);
        
    
    @Override public T read(JsonReader in) throws IOException 
        if (in.peek() == JsonToken.NULL) 
            in.nextNull();
            return null;
        
        return nameToConstant.get(in.nextInt());
    

    @Override public void write(JsonWriter out, T value) throws IOException 
        out.value(value == null ? null : constantToName.get(value));
    

    public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() 
        @SuppressWarnings("rawtypes", "unchecked")
        @Override 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(); // handle anonymous subclasses
            
            return (TypeAdapter<T>) new EnumTypeAdapter(rawType);
        
    ;

然后注册工厂。

Gson gson = new GsonBuilder()
               .registerTypeAdapterFactory(EnumTypeAdapter.ENUM_FACTORY)
               .create();

【讨论】:

【参考方案7】:

使用这个方法

GsonBuilder.enableComplexMapKeySerialization();

【讨论】:

虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。 从 gson 2.8.5 开始,为了在要用作键的枚举上使用 SerializedName 注释,这是必需的

以上是关于在使用 GSON 解析 JSON 时使用枚举的主要内容,如果未能解决你的问题,请参考以下文章

当我在 GSON(使用枚举)上运行 Proguard 时应用程序崩溃

使用 Gson 序列化和反序列化枚举 [重复]

如何在json中使用Codable解析数据有键但值与枚举不匹配

使用Gson解析json

使用 gson 解析成 POJO 时,Json 总是返回 null

使用 GSON 解析 JSON