Utilisation d'énumérations lors de l'analyse de JSON avec GSON


119

Ceci est lié à une question précédente que j'ai posée ici plus tôt

Analyse JSON à l'aide de Gson

J'essaie d'analyser le même JSON, mais maintenant j'ai un peu changé mes classes.

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

Ma classe ressemble maintenant à:

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

Ce code lève une exception,

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'exception est compréhensible, car selon la solution de ma question précédente, GSON s'attend à ce que les objets Enum soient réellement créés comme

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

Mais puisque cela est syntaxiquement impossible, quelles sont les solutions recommandées, les solutions de contournement?

Réponses:


57

À partir de la documentation de Gson :

Gson fournit la sérialisation et la désérialisation par défaut pour les Enums ... Si vous préférez changer la représentation par défaut, vous pouvez le faire en enregistrant un adaptateur de type via GsonBuilder.registerTypeAdapter (Type, Object).

Voici une de ces approches.

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

311

Je veux développer un peu la réponse NAZIK / user2724653 (pour mon cas). Voici un code 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
     }
}

dans le fichier json, vous n'avez qu'un champ "status": "N",, où N = 0,1,2,3 - dépend des valeurs de statut. C'est tout, GSONfonctionne bien avec les valeurs de la enumclasse imbriquée . Dans mon cas, j'ai analysé une liste de Itemsfrom jsonarray:

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

28
Cette réponse résout tout parfaitement, pas besoin d'adaptateurs de type!
Lena Bru

4
Lorsque je fais cela, avec Retrofit / Gson, le SerializedName des valeurs enum a des guillemets supplémentaires ajoutés. Le serveur reçoit en fait "1", par exemple, au lieu de simplement 1...
Matthew Housser

17
Que se passera-t-il si json avec le statut 5 arrivera? Existe-t-il un moyen de définir la valeur par défaut?
DmitryBorodin

8
@DmitryBorodin Si la valeur de JSON ne correspond à aucune, SerializedNamel'énumération sera par défaut null. Le comportement par défaut d'un état inconnu peut être géré dans une classe wrapper. Si toutefois vous avez besoin d'une représentation pour "inconnu" autre que, nullvous devrez écrire un désérialiseur personnalisé ou un adaptateur de type.
Peter F

32

Utiliser l'annotation @SerializedName:

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

9

Avec GSON version 2.2.2, l'énumération sera facilement regroupée et démarshallée.

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

L'extrait de code suivant supprime le besoin d'explicite Gson.registerTypeAdapter(...), en utilisant l' @JsonAdapter(class)annotation, disponible depuis Gson 2.3 (voir le commentaire 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
Veuillez noter que cette annotation n'est disponible qu'à partir de la version 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs

3
veillez à ajouter vos classes de sérialiseur / désérialiseur à votre configuration proguard, car elles pourraient être supprimées (c'est arrivé pour moi)
TormundThunderfist

2

Si vous voulez vraiment utiliser la valeur ordinale d'Enum, vous pouvez enregistrer une fabrique d'adaptateurs de type pour remplacer la fabrique par défaut de 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);
        }
    };
}

Ensuite, enregistrez simplement l'usine.

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

0

utilisez cette méthode

GsonBuilder.enableComplexMapKeySerialization();

3
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la façon et / ou pourquoi il résout le problème améliorerait la valeur à long terme de la réponse.
Nic3500

à partir de gson 2.8.5, cela est nécessaire pour utiliser les annotations SerializedName sur les énumérations que vous souhaitez utiliser comme clés
vazor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.