Menggunakan Enums saat mengurai JSON dengan GSON

119

Ini terkait dengan pertanyaan sebelumnya yang saya tanyakan di sini sebelumnya

Penguraian JSON menggunakan Gson

Saya mencoba untuk mengurai JSON yang sama, tetapi sekarang saya telah sedikit mengubah kelas saya.

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

Kelas saya sekarang terlihat seperti:

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

Kode ini memunculkan pengecualian,

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 

Pengecualian dapat dimengerti, karena sesuai dengan solusi untuk pertanyaan saya sebelumnya, GSON mengharapkan objek Enum benar-benar dibuat sebagai

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

Tetapi karena ini secara sintaksis tidak mungkin, apa solusi yang disarankan, solusinya?

Sachin Kulkarni
sumber

Jawaban:

57

Dari dokumentasi untuk Gson :

Gson menyediakan serialisasi dan deserialisasi default untuk Enums ... Jika Anda lebih memilih untuk mengubah representasi default, Anda dapat melakukannya dengan mendaftarkan adaptor tipe melalui GsonBuilder.registerTypeAdapter (Type, Object).

Berikut adalah salah satu pendekatan tersebut.

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;
  }
}
Programmer Bruce
sumber
311

Saya ingin memperluas sedikit jawaban NAZIK / user2724653 (untuk kasus saya). Berikut adalah kode 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
     }
}

di file json Anda hanya memiliki sebuah bidang "status": "N",, di mana N = 0,1,2,3 - bergantung pada nilai Status. Jadi itu saja, GSONberfungsi dengan baik dengan nilai untuk enumkelas bersarang . Dalam kasus saya, saya telah mengurai daftar Itemsdari jsonarray:

List<Item> items = new Gson().<List<Item>>fromJson(json,
                                          new TypeToken<List<Item>>(){}.getType());
validcat
sumber
28
Jawaban ini menyelesaikan semuanya dengan sempurna, tidak perlu adaptor tipe!
Lena Bru
4
Ketika saya melakukan ini, dengan Retrofit / Gson, SerializedName dari nilai enum memiliki tanda kutip ekstra yang ditambahkan. Server benar-benar menerima "1", misalnya, daripada hanya 1...
Matthew Housser
17
Apa jadinya jika json dengan status 5 akan datang? Apakah ada cara untuk menentukan nilai default?
DmitryBorodin
8
@DmitryBorodin Jika nilai dari JSON tidak cocok SerializedNamemaka enum akan default ke null. Perilaku default dari keadaan yang tidak diketahui dapat ditangani di kelas pembungkus. Namun, jika Anda memerlukan representasi untuk "tidak diketahui" selain dari nullitu, Anda perlu menulis deserializer khusus atau adaptor tipe.
Peter F
32

Gunakan anotasi @SerializedName:

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

Dengan GSON versi 2.2.2, enum akan diatur dan tidak diatur dengan mudah.

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;
  }
}
pengguna2601995
sumber
8

Potongan berikut menghilangkan kebutuhan akan eksplisit Gson.registerTypeAdapter(...), menggunakan @JsonAdapter(class)anotasi, tersedia sejak Gson 2.3 (lihat komentar 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
sumber
1
Harap perhatikan bahwa anotasi ini hanya tersedia mulai versi 2.3: google.github.io/gson/apidocs/index.html?com/google/gson/…
pm_labs
3
berhati-hatilah untuk menambahkan kelas serializer / deserializer Anda ke konfigurasi proguard Anda, karena mereka mungkin akan dihapus (itu terjadi pada saya)
TormundThunderfist
2

Jika Anda benar-benar ingin menggunakan nilai ordinal Enum, Anda dapat mendaftarkan pabrik adaptor tipe untuk menggantikan pabrik default 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);
        }
    };
}

Kemudian daftarkan saja pabriknya.

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

gunakan metode ini

GsonBuilder.enableComplexMapKeySerialization();
Ahamadullah Saikat
sumber
3
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa kode ini memecahkan masalah akan meningkatkan nilai jangka panjang jawaban.
Nic3500
pada gson 2.8.5 ini diperlukan untuk menggunakan penjelasan SerializedName pada enum yang ingin Anda gunakan sebagai kunci
vazor