Google Gson - deserialize list objek <class>? (tipe umum)

441

Saya ingin mentransfer objek daftar melalui Google Gson, tetapi saya tidak tahu bagaimana cara deserialisasi 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 dalam gerhana mengatakan "Jenis Daftar baru () {} harus menerapkan metode abstrak yang diwarisi ..." dan jika saya menggunakan perbaikan cepat saya mendapatkan monster lebih dari 20 metode bertopik.

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

Edit:

Sekarang sudah

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 lakukan JsonParseExceptions menangkap dan "hasil" tidak null.

Saya memeriksa listType dengan debugger dan mendapatkan yang berikut:

  • Jenis daftar
    • args = ListOfTypes
      • daftar = nol
      • resolvedTypes = Jenis [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Kelas (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

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

Sunting2: Saya sudah memeriksa pada Panduan Pengguna Gson . Itu menyebutkan Pengecualian Runtime yang harus terjadi selama parsing 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 dalam panduan pengguna yang disarankan. Tapi itu tidak membantu.

Sunting3: Dipecahkan, lihat jawaban saya di bawah ini.

ubur-ubur
sumber
1
Jawaban yang Anda tunjuk, gunakan TokenType. Sudahkah Anda mencobanya?
Nishant
baru saja mendapat petunjuk yang sama sebagai jawaban. lain kali saya akan memberikan contohnya dengan lebih cermat. ;)
ubur
Bisakah Anda mencoba implementasi daftar dalam jenis token? Karena tipe mentah Anda adalah daftar array, Anda harus mencoba daftar array.
uncaught_exceptions

Jawaban:

954

Metode deserialize 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, inilah penjelasan tentang bagaimana TypeTokenkelas digunakan. Konstruksi new TypeToken<...>() {}.getType()menangkap tipe waktu kompilasi (antara <dan >) ke java.lang.reflect.Typeobjek runtime . Tidak seperti Classobjek, yang hanya bisa mewakili tipe mentah (terhapus), theType objek dapat mewakili tipe apa pun dalam bahasa Java, termasuk instantiasi parameterized dari tipe generik.

The TypeTokenkelas itu sendiri tidak memiliki konstruktor publik, karena Anda tidak seharusnya untuk membangun itu secara langsung. Sebaliknya, Anda selalu membuat subclass anonim (karenanya {}, yang merupakan bagian penting dari ungkapan ini).

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

Untuk informasi lebih lanjut, lihat dokumentasi untuk TypeTokenkelas .

uncaught_exceptions
sumber
31
Dalam versi baru GSON, kontruktor TypeToken tidak terbuka untuk umum, maka di sini Anda mendapatkan kesalahan konstruktor yang tidak terlihat. Apa yang harus Anda lakukan dalam kasus ini?
Pablo
8
Menggunakan versi aktual GSON (2.2.4) itu berfungsi lagi. Anda dapat mengakses konstruktor di sini.
objek json saya dimulai dengan sebuah objek, kemudian berisi array dari objek yang saya inginkan { "myObjectArray":[ {....} , {....} , {....} ] }, saya telah membuat file model untuk {....}, bagaimana cara mendapatkan kode koleksi umum ini untuk tidak menganggap elemen root saya adalah array tanpa membuat file objek bersarang baru
CQM
7
Diperlukan impor berikut --- impor java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem
4
Ini bagus jika YourClass diperbaiki dalam kode. Bagaimana jika kelas datang saat runtime?
jasxir
273

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 mengubah array menjadi daftar dengan:

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

IMHO ini jauh lebih mudah dibaca.

Dan untuk membuatnya menjadi daftar aktual (yang dapat dimodifikasi, lihat batasan Arrays.asList()) maka lakukan saja hal berikut:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
DevNG
sumber
4
ini bagus! Bagaimana saya bisa menggunakannya dengan refleksi? Saya tidak tahu MyClassnilainya dan itu akan didefinisikan secara dinamis!
Amin Sh
1
nota: dengan ini, berhati-hatilah karena mcList bukan daftar lengkap. banyak hal tidak akan berhasil.
njzk2
4
Bagaimana cara menggunakannya dengan obat generik? T[] yourClassList = gson.fromJson(message, T[].class);// tidak dapat memilih dari variabel tipe
Pawel Cioch
2
@MateusViccari pada saat komentar itu, mcListdalam jawaban ini hanya hasil dari panggilan untuk Arrays.asList. Metode ini mengembalikan daftar yang sebagian besar jika tidak semua metode opsional dibiarkan tidak diterapkan dan melempar pengecualian. Misalnya, Anda tidak dapat menambahkan elemen apa pun ke daftar itu. Seperti yang diedit kemudian, Arrays.asListmemiliki keterbatasan, dan membungkusnya menjadi aktual ArrayListmemungkinkan Anda untuk mendapatkan daftar yang lebih berguna dalam banyak kasus.
njzk2
2
Jika Anda perlu membuat tipe array saat runtime untuk tipe elemen yang arbitrer, Anda bisa menggunakan Array.newInstance(clazz, 0).getClass()seperti yang dijelaskan dalam jawaban David Wood .
Daniel Pryden
31

Lihat posting ini. Java Type Generic 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));
}
Lebih bahagia
sumber
Apa mEntity.rulePattern?
Al Lelopath
Itu hanya objek sampel untuk diuji. Anda tidak perlu peduli tentang itu. Gunakan metode toList dan semuanya berjalan dengan baik.
Lebih bahagia
@ Happy Saya mencoba menerapkan Gson 2.8.2 ini dan sepertinya tidak berfungsi. Setiap kesempatan stackoverflow.com/questions/50743932/… Anda dapat melihatnya dan memberi tahu saya apa yang saya lewatkan
Praveen
1
@Praveen Saya sudah mencoba cara ini di 2.8.2, ini berfungsi seperti aslinya.
Lebih bahagia
31

Karena 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);
Phan Van Linh
sumber
2
TypeToken#getParameterizedterlihat jauh lebih baik daripada hack dengan subclass anonim
Nikolay Kulachenko
ini harus menjadi jawaban yang diterima; setidaknya untuk versi yang lebih baru
ccpizza
2
Ini jawaban yang sempurna. Memecahkan masalah saya.
donmj
Saya menyalin metode Anda "sebagaimana adanya" dan tidak berfungsi: kompiler mengatakan "Metode getParameterized (Kelas <List>, Kelas <T>) tidak ditentukan untuk tipe TypeToken". Saya memeriksa versi Gson saya (2.8.0) dan dokumentasi dan semuanya baik-baik saja di sisi ini ... Saya akhirnya menggunakan solusi @Happier yang berfungsi dengan baik
leguminator
@leguminator apakah Anda mengimpor TypeToken dengan benar? dan Anda menggunakan java atau kotlin. Saya akan mencoba untuk menguji lagi
Phan Van Linh
26

Wep, cara lain untuk mencapai hasil yang sama. Kami menggunakannya untuk keterbacaannya.

Alih-alih 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 mem-parsing JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);
Roc Boronat
sumber
9

Untuk Kotlin secara sederhana:

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

atau, inilah fungsi yang bermanfaat:

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

Lalu, untuk menggunakan:

val type = typeOfList<YourMagicObject>()
Chad Bingham
sumber
Saya telah menggunakan kode Anda untuk membuat fungsi ini menggunakan tipe reified: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!dan menyebutnya dengan tipe List:buildType<List<YourMagicObject>>()
coffeemakr
@coffeemakr Anda tidak perlu jenis reified di sini.
Chad Bingham
Oh Tetapi mengapa Anda membuat token jenis ArrayList buildTypedan juga memanggil fungsi dengan tipe generik? Apakah ini salah cetak? - Ini akan membuat ArrayList <ArrayList <YourMagicObject>>
coffeemakr
@coffeemakr ah, yeah. Typo
Chad Bingham
7
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, "[{...}]");
kayz1
sumber
Bagus Tapi ini duplikat DevNGjawaban di atas, ditulis 2 tahun sebelumnya: stackoverflow.com/a/17300003/1339923 (dan bacalah jawaban itu untuk peringatan untuk pendekatan ini)
Lambart
6

Saat menjawab pertanyaan awal saya, saya telah menerima jawaban doc_180, tetapi jika seseorang menghadapi masalah ini lagi, saya akan menjawab bagian kedua dari pertanyaan saya juga:

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

Kelas "MyClass" tidak memiliki konstruktor "no args", dan tidak ada yang memiliki kelas superclass. Setelah saya menambahkan konstruktor "MyClass ()" sederhana ke MyClass dan superclass-nya, semuanya bekerja dengan baik, termasuk serialisasi daftar dan deserialisasi seperti yang disarankan oleh doc_180.

ubur-ubur
sumber
1
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".
Drew
Kiat tentang menambahkan konstruktor membantu saya menyadari mengapa saya memiliki semua nilai nol. Saya memiliki nama bidang seperti "Ke" dan "Dari" di string JSON saya, tetapi bidang yang sesuai di objek saya adalah "ke" dan "dari" dalam huruf kecil, jadi mereka dilewati
Rune
4

Berikut adalah solusi yang bekerja dengan tipe yang ditentukan secara dinamis. Caranya adalah menciptakan jenis 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; 
}
David Wood
sumber
Jawaban ini akan lebih baik jika Anda terbiasa class.cast()menghindari peringatan yang tidak dicentang yang disebabkan oleh casting to (T). Atau, lebih baik lagi, jangan repot-repot casting dan gunakan sesuatu seperti Arrays.asList()untuk mengkonversi dari array ke List<T>. Juga, tidak perlu melewati panjang ke Array.newInstance()- array ukuran nol akan cukup baik untuk dipanggil getClass().
Daniel Pryden
Terima kasih atas sarannya, saya membuat perubahan yang disarankan dan memperbarui pos di atas.
David Wood
2

Saya ingin menambahkan satu kemungkinan lagi. Jika Anda tidak ingin menggunakan TypeToken dan ingin mengonversi array objek json ke ArrayList, maka 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.

Apurva Sharma
sumber
1

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

Contoh 1: Dalam deserilizeResturant ini kami menggunakan larik Karyawan [] 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());
    }
}
Ram Patro
sumber
0

Saya menyukai jawaban dari kays1 tetapi saya tidak bisa menerapkannya. Jadi saya membangun 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);
    }
}

Pemakaian:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
mike83_dev
sumber
Tentunya ini tidak berfungsi karena Anda mencoba menggunakan T pada waktu kompilasi. Ini akan secara efektif deserialize ke Daftar StringMap, bukan?
JHH
0

Dalam kasus Saya, jawaban uncaught_exceptions tidak berfungsi, saya harus menggunakan List.classalih-alih java.lang.reflect.Type:

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