Sử dụng Enums trong khi phân tích cú pháp JSON với GSON


119

Điều này liên quan đến một câu hỏi trước đó mà tôi đã hỏi ở đây trước đó

Phân tích cú pháp JSON bằng Gson

Tôi đang cố gắng phân tích cú pháp cùng một JSON, nhưng bây giờ tôi đã thay đổi các lớp của mình một chút.

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

Lớp của tôi bây giờ trông giống như:

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

Mã này ném một ngoại lệ,

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 

Ngoại lệ có thể hiểu được, bởi vì theo giải pháp cho câu hỏi trước của tôi, GSON đang mong đợi các đối tượng Enum thực sự được tạo như

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

Nhưng vì điều này là không thể về mặt cú pháp, các giải pháp, cách giải quyết được đề xuất là gì?

Câu trả lời:


57

Từ tài liệu cho Gson :

Gson cung cấp tuần tự hóa và deserialization mặc định cho Enums ... Nếu bạn muốn thay đổi biểu diễn mặc định, bạn có thể làm như vậy bằng cách đăng ký bộ điều hợp kiểu thông qua GsonBuilder.registerTypeAdapter (Loại, Đối tượng).

Sau đây là một trong những cách tiếp cận như vậy.

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

Tôi muốn mở rộng một chút câu trả lời NAZIK / user2724653 (đối với trường hợp của tôi). Đây là mã 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
     }
}

trong tệp json, bạn chỉ có một trường "status": "N",, trong đó N = 0,1,2,3 - phụ thuộc vào giá trị Trạng thái. Vì vậy, đó là tất cả, GSONhoạt động tốt với các giá trị cho enumlớp lồng nhau . Trong trường hợp của tôi, tôi đã phân tích cú pháp một danh sách Itemstừ jsonmảng:

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

28
Câu trả lời này giải quyết mọi thứ một cách hoàn hảo, không cần bộ điều hợp loại!
Lena Bru

4
Khi tôi làm điều này, với Retrofit / Gson, SerializedName của các giá trị enum có thêm dấu ngoặc kép được thêm vào. Các máy chủ thực sự nhận được "1", ví dụ, thay vì chỉ đơn giản là 1...
Matthew Housser

17
Điều gì sẽ xảy ra, nếu json với trạng thái 5 sẽ đến? Có cách nào để xác định giá trị mặc định không?
DmitryBorodin

8
@DmitryBorodin Nếu giá trị từ JSON không khớp với bất kỳ giá trị nào SerializedNamethì enum sẽ mặc định thành null. Hành vi mặc định của trạng thái không xác định có thể được xử lý trong một lớp trình bao bọc. Tuy nhiên, nếu bạn cần một đại diện cho "không xác định" nullthì bạn sẽ cần phải viết bộ chuyển đổi loại hoặc bộ chuyển đổi loại tùy chỉnh.
Peter F,

32

Sử dụng chú thích @SerializedName:

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

9

Với phiên bản GSON 2.2.2 enum sẽ được sắp xếp và bỏ quản lý một cách dễ dàng.

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

Đoạn mã sau đây loại bỏ nhu cầu rõ ràng Gson.registerTypeAdapter(...), bằng cách sử dụng @JsonAdapter(class)chú thích, có sẵn kể từ Gson 2.3 (xem nhận xét 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
Xin lưu ý rằng chú thích này chỉ có sẵn bắt đầu từ phiên bản 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs

3
hãy cẩn thận để thêm serializer / deserializer-lớp học của bạn để cấu hình Proguard của bạn, vì chúng có thể được gỡ bỏ (nó đã xảy ra cho tôi)
TormundThunderfist

2

Nếu bạn thực sự muốn sử dụng giá trị thứ tự của Enum, bạn có thể đăng ký nhà máy bộ điều hợp loại để ghi đè nhà máy mặc định của 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);
        }
    };
}

Sau đó, chỉ cần đăng ký nhà máy.

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

0

sử dụng phương pháp này

GsonBuilder.enableComplexMapKeySerialization();

3
Mặc dù mã này có thể trả lời câu hỏi, nhưng việc cung cấp thêm ngữ cảnh về cách thức và / hoặc lý do tại sao nó giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời.
Nic3500

kể từ gson 2.8.5, điều này là bắt buộc để sử dụng chú thích SerializedName trên enums mà bạn muốn sử dụng làm khóa
vazor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.