Utilizzo di Enums durante l'analisi di JSON con GSON


119

Ciò è correlato a una domanda precedente che ho posto qui prima

Analisi JSON utilizzando Gson

Sto cercando di analizzare lo stesso JSON, ma ora ho cambiato un po 'le mie classi.

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

La mia classe ora ha questo aspetto:

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

Questo codice genera un'eccezione,

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 

L'eccezione è comprensibile, perché secondo la soluzione alla mia domanda precedente, GSON si aspetta che gli oggetti Enum vengano effettivamente creati come

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

Ma poiché questo è sintatticamente impossibile, quali sono le soluzioni consigliate, soluzioni alternative?

Risposte:


57

Dalla documentazione per Gson :

Gson fornisce serializzazione e deserializzazione predefinite per Enums ... Se si preferisce modificare la rappresentazione predefinita, è possibile farlo registrando un adattatore di tipo tramite GsonBuilder.registerTypeAdapter (Type, Object).

Di seguito è riportato uno di questi approcci.

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

310

Voglio espandere un po 'la risposta NAZIK / user2724653 (per il mio caso). Ecco un codice 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
     }
}

nel file json hai solo un campo "status": "N",, dove N = 0,1,2,3 - dipende dai valori di stato. Quindi questo è tutto, GSONfunziona bene con i valori per la enumclasse nidificata . Nel mio caso ho analizzato un elenco di Itemsda jsonarray:

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

28
Questa risposta risolve tutto perfettamente, non c'è bisogno di adattatori di tipo!
Lena Bru

4
Quando lo faccio, con Retrofit / Gson, SerializedName dei valori enum ha delle virgolette aggiuntive aggiunte. Il server riceve effettivamente "1", ad esempio, invece di semplicemente 1...
Matthew Housser

17
Cosa succederà se arriverà json con stato 5? C'è un modo per definire il valore predefinito?
Dmitry Borodin

8
@DmitryBorodin Se il valore di JSON non corrisponde a nessuno, SerializedNamel'enumerazione verrà impostata automaticamente su null. Il comportamento predefinito di stato sconosciuto potrebbe essere gestito in una classe wrapper. Se tuttavia hai bisogno di una rappresentazione per "sconosciuto" diversa da nullallora, dovrai scrivere un deserializzatore personalizzato o un adattatore di tipo.
Peter F

32

Usa annotazione @SerializedName:

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

9

Con la versione 2.2.2 di GSON, enum sarà facilmente marshalling e unmarshalled.

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

8

Il frammento di codice seguente elimina la necessità di esplicito Gson.registerTypeAdapter(...), utilizzando l' @JsonAdapter(class)annotazione, disponibile a partire da Gson 2.3 (vedere il commento 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;
            }
        }
    }
}

1
Tieni presente che questa annotazione è disponibile solo a partire dalla versione 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs

3
fai attenzione ad aggiungere le tue classi di serializzatore / deserializzatore alla configurazione del tuo proguard, poiché potrebbero essere rimosse (è successo per me)
TormundThunderfist

2

Se si desidera davvero utilizzare il valore ordinale di Enum, è possibile registrare un factory di adattatori di tipo per sovrascrivere il factory predefinito di 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);
        }
    };
}

Quindi basta registrare la fabbrica.

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

0

usa questo metodo

GsonBuilder.enableComplexMapKeySerialization();

3
Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo su come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
Nic3500

a partire da gson 2.8.5 questo è necessario per utilizzare le annotazioni SerializedName sulle enumerazioni che si desidera utilizzare come chiavi
vazor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.