Google Gson - membatalkan serialisasi objek daftar‹kelas›? (tipe generik)

Saya ingin mentransfer objek daftar melalui Google Gson, tetapi saya tidak tahu cara membatalkan serialisasi tipe generik.

Apa yang saya coba setelah melihat ini ( jawaban BalusC):

MyClass mc = new Gson().fromJson(result, new List<MyClass>(){}.getClass());

tapi kemudian saya mendapatkan kesalahan di Eclipse yang mengatakan "Tipe new List(){} harus mengimplementasikan metode abstrak yang diwarisi..." dan jika saya menggunakan perbaikan cepat saya mendapatkan lebih dari 20 rintisan metode.

Saya cukup yakin ada solusi yang lebih mudah, tetapi sepertinya saya tidak dapat menemukannya!

Sunting:

Sekarang saya punya

Type listType = new TypeToken<List<MyClass>>()
                {
                }.getType();

MyClass mc = new Gson().fromJson(result, listType);

Namun, saya mendapatkan pengecualian berikut di baris "fromJson":

java.lang.NullPointerException
at org.apache.harmony.luni.lang.reflect.ListOfTypes.length(ListOfTypes.java:47)
at org.apache.harmony.luni.lang.reflect.ImplForType.toString(ImplForType.java:83)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:56)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:88)
at com.google.gson.JsonDeserializationVisitor.visitUsingCustomHandler(JsonDeserializationVisitor.java:76)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:106)
at com.google.gson.JsonDeserializationContextDefault.fromJsonArray(JsonDeserializationContextDefault.java:64)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:49)
at com.google.gson.Gson.fromJson(Gson.java:568)
at com.google.gson.Gson.fromJson(Gson.java:515)
at com.google.gson.Gson.fromJson(Gson.java:484)
at com.google.gson.Gson.fromJson(Gson.java:434)

Saya melakukan menangkap JsonParseExceptions dan "hasil" bukan nol.

Saya memeriksa listType dengan debugger dan mendapatkan yang berikut:

  • list Type
    • args = ListOfTypes
      • list = null
      • tipe terselesaikan = Tipe[ 1 ]
    • pemuat = PathClassLoader
    • ownerType0 = nol
    • ownerTypeRes = nol
    • rawType = Kelas (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

jadi sepertinya pemanggilan "getClass" tidak berfungsi dengan baik. Ada saran...?

Sunting2: Saya sudah memeriksa Gson Panduan Pengguna. Itu menyebutkan Pengecualian Runtime yang harus terjadi selama penguraian tipe generik ke Json. Saya melakukannya "salah" (tidak ditampilkan di atas), seperti pada contoh, tetapi tidak mendapatkan pengecualian sama sekali. Jadi saya mengubah serialisasi seperti yang disarankan dalam panduan pengguna. Tapi tidak membantu.

Sunting3: Terpecahkan, lihat jawaban saya di bawah.


person jellyfish    schedule 05.04.2011    source sumber
comment
Jawaban yang Anda tunjuk, menggunakan TokenType. Sudahkah Anda mencoba cara itu?   -  person Nishant    schedule 05.04.2011
comment
baru saja mendapat petunjuk yang sama sebagai jawaban. lain kali saya akan memberikan contohnya dengan melihat lebih dekat. ;)   -  person jellyfish    schedule 05.04.2011
comment
Bisakah Anda mencoba implementasi daftar dalam tipe token? Karena tipe mentah Anda adalah daftar array, Anda harus mencoba daftar array.   -  person uncaught_exceptions    schedule 06.04.2011


Jawaban (14)


Metode untuk membatalkan serialisasi koleksi generik:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

...

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> yourClassList = new Gson().fromJson(jsonArray, listType);

Karena beberapa orang di komentar telah menyebutkannya, berikut penjelasan bagaimana kelas TypeToken digunakan. Konstruksi new TypeToken<...>() {}.getType() menangkap tipe waktu kompilasi (antara < dan >) ke dalam objek runtime java.lang.reflect.Type. Berbeda dengan objek Class, yang hanya dapat mewakili tipe mentah (terhapus), objek Type dapat mewakili tipe apa pun dalam bahasa Java, termasuk contoh instance dari tipe generik yang diparameterisasi.

Kelas TypeToken sendiri tidak memiliki konstruktor publik, karena Anda tidak seharusnya membangunnya secara langsung. Sebaliknya, Anda selalu membuat subkelas anonim (karenanya {}, yang merupakan bagian penting dari ekspresi ini).

Karena penghapusan tipe, kelas TypeToken hanya mampu menangkap tipe yang diketahui sepenuhnya pada waktu kompilasi. (Artinya, Anda tidak dapat melakukan new TypeToken<List<T>>() {}.getType() untuk parameter tipe T.)

Untuk informasi lebih lanjut, lihat dokumentasi untuk kelas TypeToken.

person uncaught_exceptions    schedule 05.04.2011
comment
Di versi baru GSON, konstruktor TypeToken tidak bersifat publik, maka di sini Anda mendapatkan kesalahan konstruktor tidak terlihat. Apa yang harus Anda lakukan dalam kasus ini? - person Pablo; 04.04.2012
comment
Menggunakan versi GSON yang sebenarnya (2.2.4) ini berfungsi kembali. Anda dapat mengakses konstruktor di sini. - person ; 21.11.2013
comment
objek json saya dimulai dengan sebuah objek, kemudian berisi array dari objek yang saya inginkan { "myObjectArray":[ {....} , {....} , {....} ] }, saya telah membuat file model untuk {....}, bagaimana cara agar kode koleksi umum ini tidak menganggap elemen root saya adalah array tanpa membuat sarang baru berkas objek - person CQM; 07.02.2014
comment
paket mana yang diperlukan untuk instruksi type.getType()? Saya mendapatkan Metode getType() tidak terdefinisi untuk tipe new ArrayList‹XXXX›(){} - person Jose Ospina; 01.03.2015
comment
Ini memberi saya kesalahan jika saya menggunakan proguard saja jika tidak, itu berfungsi. ada saran - person Pratik Butani; 07.05.2015
comment
Impor berikut diperlukan --- import java.lang.reflect.Type; impor com.google.gson.reflect.TypeToken - person Umair Saleem; 30.06.2015
comment
Ini bagus jika Kelas Anda diperbaiki dalam kode. Bagaimana jika kelas datang pada saat runtime? - person jasxir; 08.09.2015
comment
maka kemungkinan besar Anda akan mengalami pengecualian pemeran kelas (java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap) saat Anda mencoba menggunakannya - person Jan Rabe; 09.11.2015
comment
Bagi saya, {} sebelum .getType terlihat sangat non-Jawa. Saya bahkan tidak bisa mengatakan apa itu secara sintaksis. Apakah itu kelas anonim yang kosong atau apa? - person Fermin Silva; 04.08.2016
comment
@CQM Anda memiliki dua cara.1-Hapus saja karakter pertama dan terakhir dari string dan Anda siap melakukannya.2-Buat Kelas, letakkan ArrayList‹Apa pun› Anda di kelas itu, Sekarang gunakan saja Apapun.kelas, Untuk argumen kedua, Anda siap melakukannya. - person steve moretz; 27.03.2019
comment
untuk kotlin val listType = object : TypeToken<ArrayList<YourClass>>() {}.type - person illusionJJ; 27.03.2019

Cara lain adalah dengan menggunakan array sebagai tipe, misalnya:

MyClass[] mcArray = gson.fromJson(jsonString, MyClass[].class);

Dengan cara ini Anda menghindari semua kerumitan dengan objek Type, dan jika Anda benar-benar membutuhkan daftar, Anda selalu dapat mengonversi array menjadi daftar dengan:

List<MyClass> mcList = Arrays.asList(mcArray);

IMHO ini jauh lebih mudah dibaca.

Dan untuk menjadikannya daftar sebenarnya (yang dapat dimodifikasi, lihat batasan Arrays.asList()) lakukan saja hal berikut:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
person DevNG    schedule 25.06.2013
comment
ini bagus! Bagaimana saya bisa menggunakannya dengan refleksi? Saya tidak tahu nilai MyClass dan itu akan ditentukan secara dinamis! - person Amin Sh; 27.12.2013
comment
catatan: dengan ini, berhati-hatilah karena mcList bukan daftar lengkap. banyak hal tidak akan berhasil. - person njzk2; 05.06.2014
comment
Bagaimana cara menggunakannya dengan obat generik? T[] yourClassList = gson.fromJson(message, T[].class); //tidak dapat memilih dari tipe variabel - person Pawel Cioch; 21.02.2015
comment
@ njzk2 Apa sebenarnya daftar lengkapnya? - person Mateus Viccari; 05.03.2017
comment
@MateusViccari pada saat berkomentar, mcList dalam jawaban ini hanyalah hasil dari panggilan ke Arrays.asList. Metode ini mengembalikan daftar yang sebagian besar, jika tidak semua, metode opsional tidak diterapkan dan memberikan pengecualian. Misalnya, Anda tidak dapat menambahkan elemen apa pun ke daftar itu. Seperti yang disarankan pada pengeditan selanjutnya, Arrays.asList memiliki keterbatasan, dan menggabungkannya menjadi ArrayList yang sebenarnya memungkinkan Anda mendapatkan daftar yang lebih berguna dalam banyak kasus. - person njzk2; 06.03.2017
comment
jangan lupa sebelum Gson gson = new Gson(); untuk pemula - person anshulkatta; 02.09.2017
comment
Jika Anda perlu membuat tipe array saat runtime untuk tipe elemen arbitrer, Anda dapat menggunakan Array.newInstance(clazz, 0).getClass() seperti yang dijelaskan di Jawaban David Wood. - person Daniel Pryden; 20.03.2018

Sejak Gson 2.8, kita dapat membuat fungsi util seperti

public <T> List<T> getList(String jsonArray, Class<T> clazz) {
    Type typeOfT = TypeToken.getParameterized(List.class, clazz).getType();
    return new Gson().fromJson(jsonArray, typeOfT);
}

Contoh menggunakan

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);
person Linh    schedule 21.08.2018
comment
TypeToken#getParameterized terlihat jauh lebih baik daripada peretasan dengan subkelas anonim - person Nikolay Kulachenko; 10.10.2018
comment
Saya menyalin metode Anda apa adanya dan tidak berfungsi: kompiler mengatakan Metode getParameterized(Class‹List›, Class‹T›) tidak ditentukan untuk tipe TypeToken. Saya memeriksa versi Gson (2.8.0) dan dokumentasi saya dan semuanya baik-baik saja di sisi ini... Saya akhirnya menggunakan solusi @Happier yang berfungsi dengan baik - person leguminator; 20.02.2020
comment
@leguminator apakah Anda mengimpor TypeToken dengan benar? dan Anda menggunakan Java atau kotlin. Saya akan mencoba menguji lagi - person Linh; 20.02.2020
comment
@PhanVanLinh Tentu saja: Saya menggunakan Java dan mengimpor com.google.gson.reflect.TypeToken dan java.lang.reflect.Type. Saya menggandakan implementasi metode pemeriksaan: ini dinyatakan sebagai TypeToken statis publik‹?› getParameterized(Type rawType, Type... typeArguments) - person leguminator; 20.02.2020
comment
Ini harus menjadi solusi yang diterima, sederhana, menggunakan API Gson, dan tidak ada peretasan di sekitarnya. +1 - person 4gus71n; 29.08.2020

Lihat posting ini. Jenis Java Generik sebagai Argumen untuk GSON

Saya punya solusi yang lebih baik untuk ini. Inilah kelas pembungkus untuk daftar sehingga pembungkus dapat menyimpan jenis daftar yang tepat.

public class ListOfJson<T> implements ParameterizedType
{
  private Class<?> wrapped;

  public ListOfJson(Class<T> wrapper)
  {
    this.wrapped = wrapper;
  }

  @Override
  public Type[] getActualTypeArguments()
  {
      return new Type[] { wrapped };
  }

  @Override
  public Type getRawType()
  {
    return List.class;
  }

  @Override
  public Type getOwnerType()
  {
    return null;
  }
}

Dan kemudian, kodenya bisa sederhana:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}
person Happier    schedule 16.10.2014
comment
Apa itu mEntity.rulePattern? - person Al Lelopath; 07.04.2015
comment
Itu hanya objek sampel untuk pengujian. Anda tidak perlu mempedulikannya. Gunakan metode toList dan semuanya berjalan dengan baik. - person Happier; 08.04.2015

Ya, cara lain untuk mencapai hasil yang sama. Kami menggunakannya agar mudah dibaca.

Daripada melakukan kalimat yang sulit dibaca ini:

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonArray, listType);

Buat kelas kosong yang memperluas Daftar objek Anda:

public class YourClassList extends ArrayList<YourClass> {}

Dan gunakan saat mengurai JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);
person Roc Boronat    schedule 27.11.2015

Untuk Kotlin secara sederhana:

import java.lang.reflect.Type
import com.google.gson.reflect.TypeToken
...
val type = object : TypeToken<List<T>>() {}.type

atau, ini fungsi yang berguna:

fun <T> typeOfList(): Type {
    return object : TypeToken<List<T>>() {}.type
}

Kemudian, untuk menggunakan:

val type = typeOfList<YourMagicObject>()
person Chad Bingham    schedule 25.10.2017
comment
Saya telah menggunakan kode Anda untuk membuat fungsi ini menggunakan tipe reified: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!! dan memanggilnya dengan tipe Daftar: buildType<List<YourMagicObject>>() - person coffeemakr; 21.11.2017
comment
@coffeemakr Anda tidak memerlukan tipe reifikasi di sini. - person Chad Bingham; 22.11.2017
comment
Oh. Tetapi mengapa Anda membuat token tipe ArrayList di buildType dan juga memanggil fungsi dengan tipe generik? Apakah ini salah ketik? - Ini akan membuat ArrayList‹ArrayList‹YourMagicObject›› - person coffeemakr; 23.11.2017
comment
@coffeemakr ah, ya. Salah ketik - person Chad Bingham; 23.11.2017

public static final <T> List<T> getList(final Class<T[]> clazz, final String json)
{
    final T[] jsonToObject = new Gson().fromJson(json, clazz);

    return Arrays.asList(jsonToObject);
}

Contoh:

getList(MyClass[].class, "[{...}]");
person kayz1    schedule 02.03.2015
comment
Bagus. Tapi ini duplikat jawaban DevNGs di atas, ditulis 2 tahun sebelumnya: stackoverflow.com/a/17300003/1339923 (dan baca jawaban itu untuk peringatan terhadap pendekatan ini) - person Lambart; 27.09.2017

Karena menjawab pertanyaan awal saya, saya telah menerima jawaban doc_180, tetapi jika seseorang mengalami masalah ini lagi, saya akan menjawab paruh kedua pertanyaan saya juga:

NullPointerError yang saya jelaskan tidak ada hubungannya dengan Daftar itu sendiri, tetapi dengan isinya!

Kelas "MyClass" tidak memiliki konstruktor "no args", dan juga tidak memiliki superclassnya. Setelah saya menambahkan konstruktor "MyClass()" sederhana ke MyClass dan superclassnya, semuanya berfungsi dengan baik, termasuk serialisasi Daftar dan deserialisasi seperti yang disarankan oleh doc_180.

person jellyfish    schedule 08.04.2011
comment
Jika Anda memiliki daftar kelas abstrak Anda akan mendapatkan kesalahan yang sama. Saya kira ini adalah pesan kesalahan umum GSON untuk Tidak dapat membuat instance kelas. - person Drew; 30.09.2011
comment
Tip tentang menambahkan konstruktor membantu saya menyadari mengapa saya memiliki semua nilai nol. Saya memiliki nama bidang seperti Ke dan Dari dalam string JSON saya, tetapi bidang terkait di objek saya adalah ke dan dari dalam huruf kecil, jadi bidang tersebut dilewati - person Rune; 19.06.2016

Berikut adalah solusi yang bekerja dengan tipe yang ditentukan secara dinamis. Caranya adalah membuat tipe array yang tepat menggunakan Array.newInstance().

    public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
    Object [] array = (Object[])java.lang.reflect.Array.newInstance(clazz, 0);
    array = gson.fromJson(json, array.getClass());
    List<T> list = new ArrayList<T>();
    for (int i=0 ; i<array.length ; i++)
        list.add(clazz.cast(array[i]));
    return list; 
}
person David Wood    schedule 31.03.2016

Saya ingin menambahkan satu kemungkinan lagi. Jika Anda tidak ingin menggunakan TypeToken dan ingin mengonversi array objek json menjadi ArrayList, Anda dapat melanjutkan seperti ini:

Jika struktur json Anda seperti:

{

"results": [
    {
        "a": 100,
        "b": "value1",
        "c": true
    },
    {
        "a": 200,
        "b": "value2",
        "c": false
    },
    {
        "a": 300,
        "b": "value3",
        "c": true
    }
]

}

dan struktur kelas Anda seperti:

public class ClassName implements Parcelable {

    public ArrayList<InnerClassName> results = new ArrayList<InnerClassName>();
    public static class InnerClassName {
        int a;
        String b;
        boolean c;      
    }
}

maka Anda dapat menguraikannya seperti:

Gson gson = new Gson();
final ClassName className = gson.fromJson(data, ClassName.class);
int currentTotal = className.results.size();

Sekarang Anda dapat mengakses setiap elemen objek className.

person Apurva Sharma    schedule 14.08.2015

Lihat contoh 2 untuk pemahaman kelas 'Tipe' tentang Gson.

Contoh 1: Dalam deserilizeResturant ini kami menggunakan array Employee[] dan mendapatkan detailnya

public static void deserializeResturant(){

       String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
       Gson gson = new Gson();
       Employee[] emp = gson.fromJson(empList, Employee[].class);
       int numberOfElementInJson = emp.length();
       System.out.println("Total JSON Elements" + numberOfElementInJson);
       for(Employee e: emp){
           System.out.println(e.getName());
           System.out.println(e.getEmpId());
       }
   }

Contoh 2:

//Above deserilizeResturant used Employee[] array but what if we need to use List<Employee>
public static void deserializeResturantUsingList(){

    String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
    Gson gson = new Gson();

    // Additionally we need to se the Type then only it accepts List<Employee> which we sent here empTypeList
    Type empTypeList = new TypeToken<ArrayList<Employee>>(){}.getType();


    List<Employee> emp = gson.fromJson(empList, empTypeList);
    int numberOfElementInJson = emp.size();
    System.out.println("Total JSON Elements" + numberOfElementInJson);
    for(Employee e: emp){
        System.out.println(e.getName());
        System.out.println(e.getEmpId());
    }
}
person Ram Patro    schedule 10.09.2017

Saya menyukai jawaban dari kays1 tetapi saya tidak bisa menerapkannya. Jadi saya membuat versi saya sendiri menggunakan konsepnya.

public class JsonListHelper{
    public static final <T> List<T> getList(String json) throws Exception {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        Type typeOfList = new TypeToken<List<T>>(){}.getType();
        return gson.fromJson(json, typeOfList);
    }
}

Penggunaan:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
person mike83_dev    schedule 20.10.2015
comment
Tentunya ini tidak dapat berfungsi karena Anda mencoba menggunakan T pada waktu kompilasi. Ini secara efektif akan melakukan deserialisasi ke Daftar StringMap, bukan? - person JHH; 04.04.2017

Dalam kasus saya, jawaban @uncaught_Exceptions tidak berhasil, saya harus menggunakan List.class alih-alih java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
person Andrei    schedule 24.03.2017

Saya telah membuat lib GsonUtils untuk kasus ini. Saya menambahkan ini ke dalam repositori pusat maven.

Map<String, SimpleStructure> expected = new HashMap<>();
expected.put("foo", new SimpleStructure("peperoni"));

String json = GsonUtils.writeValue(expected);

Map<String, SimpleStructure> actual = GsonUtils.readMap(json, String.class, SimpleStructure.class);
person oleg.cherednik    schedule 11.01.2021