GSON으로 JSON을 구문 분석하는 동안 열거 형 사용


119

이것은 이전에 여기에서 물었던 이전 질문과 관련이 있습니다.

Gson을 사용한 JSON 파싱

동일한 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
이 답변은 모든 것을 완벽하게 해결하며 유형 어댑터가 필요하지 않습니다!
Lena Bru 2014-06-11

4
이렇게하면 Retrofit / Gson을 사용하여 열거 형 값의 SerializedName에 추가 따옴표가 추가됩니다. 서버는 실제로받는 "1"대신 단순히, 예를 들어, 1...
마태 복음 Housser에게

17
상태가 5 인 json이 도착하면 어떻게됩니까? 기본값을 정의하는 방법이 있습니까?
DmitryBorodin 2015

8
@DmitryBorodin JSON의 값이 일치하지 않는 SerializedName경우 열거 형은 기본적으로 null. 알 수없는 상태의 기본 동작은 래퍼 클래스에서 처리 할 수 ​​있습니다. 그러나 "알 수 없음"이외의 표현 null이 필요한 경우 사용자 지정 deserializer 또는 유형 어댑터를 작성해야합니다.
Peter F

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 2.3부터 사용할 수 Gson.registerTypeAdapter(...)있는 @JsonAdapter(class)주석을 사용하여 explicit에 대한 필요성을 제거합니다 ( 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
그들은 제거 될 수 있습니다로, 당신의 난독 설정에 시리얼 라이저 / 디시리얼라이저 - 클래스를 추가 할주의 (나를 위해 일어)
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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.