Використання Enums під час розбору JSON з GSON


119

Це пов’язано з попереднім питанням, яке я тут задавав раніше

JSON розбирає за допомогою 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}");

Але оскільки це синтаксично неможливо, які рекомендовані рішення вирішують?

Відповіді:


57

З документації на 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;
  }
}

310

Я хочу трохи розширити відповідь 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класу. У моєму випадку я розібрав список Itemsз jsonмасиву:

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

28
Ця відповідь вирішує все ідеально, немає необхідності в адаптерах типу!
Лена Бру,

4
Коли я це роблю, за допомогою Retrofit / Gson, у SerializedName значень перерахунків додано додаткові лапки. Сервер насправді отримує "1", наприклад, замість того, щоб просто 1...
Matthew Housser

17
Що буде, якщо json зі статусом 5 прибуде? Чи є спосіб визначити значення за замовчуванням?
ДмитроБородін

8
@DmitryBorodin Якщо значення JSON не відповідає жодному, SerializedNameто перерахунок за замовчуванням буде null. Поведінка за умовчанням невідомого стану може оброблятися в класі обгортки. Якщо вам потрібне представлення для "невідомого", крім nullтого, вам потрібно буде написати спеціальний десеріалізатор або адаптер типу.
Петро F

32

Використовувати примітку @SerializedName:

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

9

З GSON версією 2.2.2 enum буде легко скасовано та неможливим.

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

Наступний фрагмент усуває необхідність явного Gson.registerTypeAdapter(...)використання @JsonAdapter(class)приміток, доступних з Gson 2.3 (див. Коментар 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
Зауважте, що ця примітка доступна лише з початкової версії 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs

3
будьте обережні, щоб додати свої класи серіалізатора / десеріалізатора до своєї конфігурації proguard, оскільки вони можуть бути видалені (це сталося зі мною)
TormundThunderfist

2

Якщо ви дійсно хочете використовувати порядкове значення 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();

0

використовувати цей метод

GsonBuilder.enableComplexMapKeySerialization();

3
Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, як та / або чому він вирішує проблему, покращить довгострокове значення відповіді.
Nic3500

станом на gson 2.8.5 це потрібно для використання анотацій SerializedName про переліки, які ви хочете використовувати як ключі
vazor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.