Verwenden von Enums beim Parsen von JSON mit GSON

119

Dies hängt mit einer früheren Frage zusammen, die ich hier zuvor gestellt habe

JSON-Analyse mit Gson

Ich versuche, denselben JSON zu analysieren, aber jetzt habe ich meine Klassen ein wenig geändert.

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

Meine Klasse sieht jetzt so aus:

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

Dieser Code löst eine Ausnahme aus.

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 

Die Ausnahme ist verständlich, da GSON gemäß der Lösung meiner vorherigen Frage erwartet, dass die Enum-Objekte tatsächlich als erstellt werden

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

Aber da dies syntaktisch unmöglich ist, was sind die empfohlenen Lösungen, Problemumgehungen?

Sachin Kulkarni
quelle

Antworten:

57

Aus der Dokumentation für Gson :

Gson bietet Standard-Serialisierung und -Deserialisierung für Enums ... Wenn Sie die Standarddarstellung ändern möchten, können Sie dies tun, indem Sie einen Typadapter über GsonBuilder.registerTypeAdapter (Typ, Objekt) registrieren.

Das Folgende ist ein solcher Ansatz.

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;
  }
}
Programmierer Bruce
quelle
309

Ich möchte die Antwort von NAZIK / user2724653 (für meinen Fall) etwas erweitern. Hier ist ein Java-Code:

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

In der JSON-Datei haben Sie nur ein Feld "status": "N",, in dem N = 0,1,2,3 - abhängig von den Statuswerten. Das ist alles, GSONfunktioniert gut mit den Werten für die verschachtelte enumKlasse. In meinem Fall habe ich eine Liste von Itemsaus jsonArray analysiert :

List<Item> items = new Gson().<List<Item>>fromJson(json,
                                          new TypeToken<List<Item>>(){}.getType());
gültige Katze
quelle
28
Diese Antwort löst alles perfekt, es werden keine Typadapter benötigt!
Lena Bru
4
Wenn ich dies mit Retrofit / Gson mache, werden dem SerializedName der Enum-Werte zusätzliche Anführungszeichen hinzugefügt. Der Server empfängt "1"zum Beispiel tatsächlich statt einfach 1...
Matthew Housser
17
Was passiert, wenn json mit Status 5 ankommt? Gibt es eine Möglichkeit, den Standardwert zu definieren?
DmitryBorodin
8
@DmitryBorodin Wenn der Wert von JSON mit keinem übereinstimmt, SerializedNamewird standardmäßig die Aufzählung verwendet null. Das Standardverhalten eines unbekannten Status kann in einer Wrapper-Klasse behandelt werden. Wenn Sie jedoch eine andere Darstellung für "unbekannt" nullbenötigen, müssen Sie einen benutzerdefinierten Deserializer oder Typadapter schreiben.
Peter F
32

Anmerkung verwenden @SerializedName:

@SerializedName("${title}")
TITLE,
@SerializedName("${description}")
DESCRIPTION
Inc.
quelle
9

Mit GSON Version 2.2.2 kann enum problemlos gemarshallt und nicht gemarshallt werden.

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
quelle
8

Das folgende Snippet beseitigt die Notwendigkeit einer expliziten Gson.registerTypeAdapter(...)Verwendung der @JsonAdapter(class)Annotation, die seit Gson 2.3 verfügbar ist (siehe Kommentar 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
quelle
1
Bitte beachten Sie, dass diese Anmerkung nur ab Version 2.3 verfügbar ist: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs
3
Achten Sie darauf, Ihre Serializer / Deserializer-Klassen zu Ihrer Proguard-Konfiguration hinzuzufügen, da sie möglicherweise entfernt werden (es ist mir passiert)
TormundThunderfist
2

Wenn Sie den Ordnungswert der Enum wirklich verwenden möchten, können Sie eine Typadapterfactory registrieren, um die Standardfactory von Gson zu überschreiben.

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

Dann registrieren Sie einfach die Fabrik.

Gson gson = new GsonBuilder()
               .registerTypeAdapterFactory(EnumTypeAdapter.ENUM_FACTORY)
               .create();
Tom Bollwitt
quelle
0

Verwenden Sie diese Methode

GsonBuilder.enableComplexMapKeySerialization();
Ahamadullah Saikat
quelle
3
Während dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Nic3500
Ab gson 2.8.5 ist dies erforderlich, um SerializedName-Annotationen für Aufzählungen zu verwenden, die Sie als Schlüssel verwenden
möchten