Używanie wyliczeń podczas analizowania JSON z GSON

119

Jest to związane z poprzednim pytaniem, które zadałem tutaj wcześniej

Analiza JSON przy użyciu Gson

Próbuję przeanalizować ten sam kod JSON, ale teraz trochę zmieniłem moje klasy.

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

Moja klasa wygląda teraz tak:

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

Ten kod zgłasza wyjątek,

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 

Wyjątek jest zrozumiały, ponieważ zgodnie z rozwiązaniem mojego poprzedniego pytania, GSON oczekuje, że obiekty Enum zostaną faktycznie utworzone jako

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

Ale skoro jest to niemożliwe pod względem składniowym, jakie są zalecane rozwiązania i obejścia?

Sachin Kulkarni
źródło

Odpowiedzi:

57

Z dokumentacji dla Gson :

Gson zapewnia domyślną serializację i deserializację dla wyliczeń ... Jeśli wolisz zmienić domyślną reprezentację, możesz to zrobić, rejestrując adapter typu za pomocą GsonBuilder.registerTypeAdapter (Type, Object).

Oto jedno z takich podejść.

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;
  }
}
Programista Bruce
źródło
310

Chcę nieco rozwinąć odpowiedź NAZIK / user2724653 (dla mojego przypadku). Oto kod 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
     }
}

w pliku json masz tylko pole "status": "N",, gdzie N = 0,1,2,3 - zależy od wartości Status. To wszystko, GSONdziała dobrze z wartościami dla enumklasy zagnieżdżonej . W moim przypadku przeanalizowałem listę Itemsz jsontablicy:

List<Item> items = new Gson().<List<Item>>fromJson(json,
                                          new TypeToken<List<Item>>(){}.getType());
validcat
źródło
28
Ta odpowiedź rozwiązuje wszystko doskonale, nie ma potrzeby stosowania adapterów typu!
Lena Bru
4
Kiedy to robię, z Retrofit / Gson, do SerializedName wartości wyliczenia dodano dodatkowe cudzysłowy. Serwer faktycznie odbiera "1", na przykład, zamiast po prostu 1...
Matthew Housser
17
Co się stanie, jeśli nadejdzie json ze statusem 5? Czy istnieje sposób zdefiniowania wartości domyślnej?
DmitryBorodin
8
@DmitryBorodin Jeśli wartość z JSON nie pasuje do żadnej, SerializedNamewyliczenie będzie domyślnie null. Domyślne zachowanie o nieznanym stanie można obsłużyć w klasie opakowania. Jeśli jednak potrzebujesz reprezentacji dla „nieznanego” innego niż nullwtedy, będziesz musiał napisać niestandardowy deserializator lub typ adaptera.
Peter F
32

Użyj adnotacji @SerializedName:

@SerializedName("${title}")
TITLE,
@SerializedName("${description}")
DESCRIPTION
Inc
źródło
9

Z GSON w wersji 2.2.2 wyliczenia będą łatwo kierowane i usuwane.

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;
  }
}
user2601995
źródło
8

Poniższy fragment kodu eliminuje potrzebę jawnego Gson.registerTypeAdapter(...)używania @JsonAdapter(class)adnotacji dostępnej od wersji Gson 2.3 (patrz komentarz 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;
            }
        }
    }
}
Wout
źródło
1
Należy pamiętać, że ta adnotacja jest dostępna tylko od wersji 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/ ...
pm_labs
3
uważaj, aby dodać klasy serializatora / deserializatora do konfiguracji proguard, ponieważ mogą zostać usunięte (tak się stało w moim przypadku)
Tormund Thunderfist
2

Jeśli naprawdę chcesz użyć wartości porządkowej Enuma, możesz zarejestrować fabrykę adaptera typu, aby zastąpić domyślną fabrykę GSona.

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

Następnie po prostu zarejestruj fabrykę.

Gson gson = new GsonBuilder()
               .registerTypeAdapterFactory(EnumTypeAdapter.ENUM_FACTORY)
               .create();
Tom Bollwitt
źródło
0

użyj tej metody

GsonBuilder.enableComplexMapKeySerialization();
Ahamadullah Saikat
źródło
3
Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Nic3500,
od wersji 2.8.5 gson jest to wymagane, aby używać adnotacji SerializedName na wyliczeniach, których chcesz używać jako kluczy
vazor