Google Gson - deserializować obiekt listy <klasa>? (rodzaj ogólny)

441

Chcę przenieść obiekt listy za pośrednictwem Google Gson, ale nie wiem, jak dokonać deserializacji typów ogólnych.

Co próbowałem po obejrzeniu tego (odpowiedź BalusC):

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

ale wtedy pojawia się błąd w zaćmieniu, mówiąc: „Typ new List () {} musi implementować odziedziczoną metodę abstrakcyjną ...” i jeśli użyję szybkiej poprawki, dostanę potwora ponad 20 kodów pośredniczących metody.

Jestem prawie pewien, że istnieje łatwiejsze rozwiązanie, ale wydaje się, że nie jestem w stanie go znaleźć!

Edytować:

Teraz mam

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

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

Jednak w wierszu „fromJson” pojawia się następujący wyjątek:

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)

I zrobić JsonParseExceptions połowów i „wynik” nie jest null.

Sprawdziłem listType za pomocą debugera i otrzymałem następujące informacje:

  • typ listy
    • args = ListOfTypes
      • list = null
      • resolvedTypes = Typ [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Klasa (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

więc wygląda na to, że wywołanie „getClass” nie działało poprawnie. Jakieś sugestie...?

Edycja2: Sprawdziłem w Podręczniku użytkownika Gson . Wspomina wyjątek czasu wykonywania, który powinien wystąpić podczas analizowania typu ogólnego dla Jsona. Zrobiłem to „źle” (nie pokazano powyżej), tak jak w przykładzie, ale w ogóle nie dostałem tego wyjątku. Zmieniłem więc serializację zgodnie z sugerowanym podręcznikiem użytkownika. Jednak nie pomogło.

Edycja3: Rozwiązane, patrz moja odpowiedź poniżej.

Meduza
źródło
1
Odpowiedź, na którą wskazałeś, wykorzystuje TokenType. Próbowałeś w ten sposób?
Nishant,
właśnie dostałem taką samą wskazówkę jak odpowiedź. następnym razem przyjrzę się temu przykładowi. ;)
meduza
Czy możesz wypróbować implementację listy w tokenie typu? Ponieważ twoim typem surowym jest lista tablic, powinieneś spróbować listy tablic.
uncaught_exceptions

Odpowiedzi:

954

Metoda deserializacji ogólnej kolekcji:

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

Ponieważ kilka osób w komentarzach wspomniało o tym, oto wyjaśnienie, w jaki sposób TypeTokenużywana jest klasa. Konstrukcja new TypeToken<...>() {}.getType()przechwytuje typ czasu kompilacji (między <i >) do java.lang.reflect.Typeobiektu wykonawczego . W przeciwieństwie do Classobiektu, który może reprezentować tylko surowy (wymazany) typ, Typeobiekt może reprezentować dowolny typ w języku Java, w tym sparametryzowaną instancję typu ogólnego.

Sama TypeTokenklasa nie ma publicznego konstruktora, ponieważ nie należy jej konstruować bezpośrednio. Zamiast tego zawsze konstruujesz anonimową podklasę (stąd {}, która jest niezbędną częścią tego wyrażenia).

Z powodu wymazywania typu TypeTokenklasa jest w stanie przechwytywać tylko te typy, które są w pełni znane w czasie kompilacji. (Oznacza to, że nie można tego zrobić new TypeToken<List<T>>() {}.getType()dla parametru typu T).

Aby uzyskać więcej informacji, zobacz dokumentację TypeTokenklasy .

uncaught_exceptions
źródło
31
W nowych wersjach GSON konstruktor TypeToken nie jest publiczny, dlatego tutaj pojawia się niewidoczny błąd konstruktora. Co musisz zrobić w tym przypadku?
Pablo,
8
Korzystanie z aktualnej wersji GSON (2.2.4) działa ponownie. Możesz uzyskać dostęp do konstruktora tutaj.
mój obiekt json zaczyna się od obiektu, a następnie zawiera tablicę obiektu, dla którego chcę { "myObjectArray":[ {....} , {....} , {....} ] }, stworzyłem plik modelu {....}, w jaki sposób uzyskać ten ogólny kod kolekcji, aby nie zakładać, że mój element główny jest tablicą bez tworzenia nowego pliku zagnieżdżonego
CQM
7
Wymagane są następujące operacje importowania --- import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem
4
Jest to dobre, jeśli YourClass jest ustawiony w kodzie. Co się stanie, jeśli klasa przyjdzie w czasie wykonywania?
jasxir
273

Innym sposobem jest użycie tablicy jako typu, np .:

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

W ten sposób unikniesz kłopotów z obiektem Type, a jeśli naprawdę potrzebujesz listy, zawsze możesz przekonwertować tablicę na listę poprzez:

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

IMHO jest to o wiele bardziej czytelne.

Aby stała się rzeczywistą listą (którą można modyfikować, zobacz ograniczenia Arrays.asList()), wykonaj następujące czynności:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
DevNG
źródło
4
to jest świetne! Jak mogę używać tego z odbiciem? Nie znam MyClasswartości, która zostanie zdefiniowana dynamicznie!
Amin Sh
1
nota: przy tym uważaj, aby mcList nie była pełnoprawną listą. wiele rzeczy nie zadziała.
njzk2
4
Jak używać go z lekami generycznymi? T[] yourClassList = gson.fromJson(message, T[].class);// nie można wybrać zmiennej typu
Paweł Cioch
2
@MateusViccari w momencie tego komentarza, mcListw tej odpowiedzi był tylko wynikiem połączenia z Arrays.asList. Ta metoda zwraca listę, na której większość, jeśli nie wszystkie metody opcjonalne, pozostają niezaimplementowane i wyrzuca wyjątki. Na przykład nie można dodać żadnego elementu do tej listy. Jak sugeruje późniejsza edycja, Arrays.asListma ograniczenia, a zawinięcie jej w rzeczywistą ArrayListpozwala uzyskać listę, która jest bardziej przydatna w wielu przypadkach.
njzk2
2
Jeśli chcesz w czasie wykonywania skonstruować typ tablicy dla dowolnego typu elementu, możesz użyć Array.newInstance(clazz, 0).getClass()zgodnie z opisem w odpowiedzi Davida Wooda .
Daniel Pryden
31

Zobacz ten post. Rodzaj Java Generic jako argument dla GSON

Mam na to lepsze rozwiązanie. Oto klasa opakowania dla listy, dzięki czemu opakowanie może przechowywać dokładnie typ listy.

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

A potem kod może być prosty:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}
Szczęśliwie
źródło
Co to jest mEntity.rulePattern?
Al Lelopath,
To tylko przykładowy obiekt do testu. Nie musisz się tym przejmować. Użyj metody toList i wszystko pójdzie dobrze.
Szczęśliwy
@Happier Próbuję wdrożyć Gson 2.8.2 i wydaje się, że nie działa. Każda szansa stackoverflow.com/questions/50743932/ ... możesz rzucić okiem i dać mi znać, czego mi brakuje
Praveen
1
@Praveen Próbowałem w ten sposób w 2.8.2, działa jak oryginał.
Szczęśliwszy
31

Ponieważ Gson 2.8możemy stworzyć funkcję util jak

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

Przykład użycia

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);
Phan Van Linh
źródło
2
TypeToken#getParameterizedwygląda o wiele lepiej niż hack z anonimową podklasą
Nikolay Kulachenko,
to powinna być zaakceptowana odpowiedź; przynajmniej dla nowszych wersji
ccpizza
2
To idealna odpowiedź. Rozwiązuje mój problem.
donmj
Skopiowałem twoją metodę „jak jest” i to nie działa: kompilator mówi „Metoda getParameterized (klasa <Lista>, klasa <T>) jest niezdefiniowana dla typu TypeToken”. Sprawdziłem zarówno moją wersję Gson (2.8.0), jak i dokumentację i wszystko jest w porządku po tej stronie ... Skończyło się na rozwiązaniu @Happier, które działa dobrze
leguminator
@leguminator zaimportowałeś poprawny TypeToken? i używasz java lub kotlin. Spróbuję ponownie przetestować
Phan Van Linh
26

Wep, kolejny sposób na osiągnięcie tego samego rezultatu. Używamy go ze względu na jego czytelność.

Zamiast robić to trudne do odczytania zdanie:

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

Utwórz pustą klasę, która rozszerza listę twojego obiektu:

public class YourClassList extends ArrayList<YourClass> {}

I użyj go podczas analizowania JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);
Roc Boronat
źródło
9

W przypadku Kotlin po prostu:

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

lub tutaj jest przydatna funkcja:

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

Następnie, aby użyć:

val type = typeOfList<YourMagicObject>()
Chad Bingham
źródło
Użyłem twojego kodu, aby utworzyć tę funkcję przy użyciu zweryfikowanych typów: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!i wywołaj go z typem listy:buildType<List<YourMagicObject>>()
coffeemakr
@coffeemakr Nie potrzebujesz tutaj zweryfikowanych typów.
Chad Bingham
O. Ale dlaczego tworzysz token typu ArrayList w, buildTypea także wywołujesz funkcję z typem ogólnym? Czy to literówka? - Spowoduje to utworzenie 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);
}

Przykład:

getList(MyClass[].class, "[{...}]");
kayz1
źródło
Dobry. Ale to duplikat DevNGpowyższej odpowiedzi, napisanej 2 lata wcześniej: stackoverflow.com/a/17300003/1339923 (i przeczytaj tę odpowiedź na zastrzeżenia do tego podejścia)
Lambart
6

Odpowiadając na moje pierwotne pytanie, zaakceptowałem odpowiedź doc_180, ale jeśli ktoś ponownie napotka ten problem, odpowiem również na drugą połowę mojego pytania:

Opisany przeze mnie błąd NullPointerError nie miał nic wspólnego z samą listą, ale z jej zawartością!

Klasa „MyClass” nie miała konstruktora „bez argumentów” i żadna z nich nie miała swojej nadklasy. Po dodaniu prostego konstruktora „MyClass ()” do MyClass i jego nadklasy wszystko działało dobrze, łącznie z serializacją listy i deserializacją, zgodnie z sugestią doc_180.

Meduza
źródło
1
Jeśli masz listę klas abstrakcyjnych, otrzymasz ten sam błąd. To chyba ogólny komunikat o błędzie GSON dotyczący „Nie można utworzyć instancji klasy”.
Drew
Porada na temat dodawania konstruktora pomogła mi zrozumieć, dlaczego wszystkie wartości zerowe są dostępne. Miałem nazwy pól takie jak „Do” i „From” w moim ciągu JSON, ale odpowiadające im pola w moim obiekcie to „to” i „from” pisane małymi literami, więc zostały pominięte
Rune
4

Oto rozwiązanie, które działa z dynamicznie zdefiniowanym typem. Sztuką jest stworzenie odpowiedniego typu tablicy za pomocą 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
źródło
Ta odpowiedź byłaby lepsza, gdybyś class.cast()uniknął niesprawdzonego ostrzeżenia spowodowanego przesyłaniem do (T). Lub jeszcze lepiej, nie przejmuj się rzutowaniem i użyj czegoś takiego jak Arrays.asList()konwersja z tablicy na a List<T>. Ponadto nie trzeba podawać długości do Array.newInstance()- tablica o rozmiarze zero będzie wystarczająca do wywołania getClass().
Daniel Pryden,
Dzięki za sugestię, wprowadziłem sugerowane zmiany i zaktualizowałem powyższy post.
David Wood
2

Chcę dodać jeszcze jedną możliwość. Jeśli nie chcesz używać TypeToken i chcesz przekonwertować tablicę obiektów json na ArrayList, możesz postępować w ten sposób:

Jeśli twoja struktura json jest podobna:

{

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

}

a twoja struktura klas wygląda następująco:

public class ClassName implements Parcelable {

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

możesz to parsować w następujący sposób:

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

Teraz możesz uzyskać dostęp do każdego elementu obiektu className.

Apurva Sharma
źródło
1

Zobacz przykład 2, aby zapoznać się z klasą Gson dla klasy „Type”.

Przykład 1: W tym dezilateResturant użyliśmy tablicy Employee [] i uzyskaliśmy szczegóły

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

Przykład 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
źródło
0

Podobała mi się odpowiedź z kays1, ale nie mogłem jej zaimplementować. Więc zbudowałem własną wersję, korzystając z jego koncepcji.

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

Stosowanie:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
mike83_dev
źródło
Z pewnością nie może to działać, ponieważ próbujesz używać T w czasie kompilacji. To skutecznie deserializuje do listy StringMap, nie?
JHH
0

W moim przypadku odpowiedź @ uncaught_exceptions nie zadziałała, musiałem użyć List.classzamiast java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
Andrei
źródło