Использование 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;
  }
}

311

Хочу немного расширить ответ 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 - зависит от значений Status. Итак, все, 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...
Мэтью Хуссер,

17
Что будет, если придет json со статусом 5? Есть ли способ определить значение по умолчанию?
Дмитрий Бородин

8
@DmitryBorodin Если значение из JSON не совпадает ни с одним, SerializedNameто по умолчанию будет указано перечисление null. Поведение по умолчанию неизвестного состояния может быть обработано в классе-оболочке. Однако, если вам нужно представление для «unknown», кроме nullэтого, вам нужно будет написать собственный десериализатор или адаптер типа.
Питер Ф,

32

Используйте аннотацию @SerializedName:

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

9

В GSON версии 2.2.2 перечисление будет легко маршалироваться и демаршалироваться.

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 01
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.