Google Gson - desserializar o objeto <class> da lista? (tipo genérico)

441

Quero transferir um objeto de lista pelo Google Gson, mas não sei desserializar tipos genéricos.

O que eu tentei depois de olhar para isso (resposta de BalusC):

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

mas, em seguida, recebo um erro no eclipse dizendo "O tipo new List () {} deve implementar o método abstrato herdado ..." e, se eu usar uma correção rápida, recebo um monstro com mais de 20 stubs de métodos.

Tenho certeza de que existe uma solução mais fácil, mas não consigo encontrá-la!

Editar:

Agora eu tenho

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

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

No entanto, recebo a seguinte exceção na linha "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)

I fazer JsonParseExceptions de captura e "resultado" não é nulo.

Eu verifiquei listType com o depurador e obtive o seguinte:

  • Tipo de lista
    • args = ListOfTypes
      • list = null
      • resolvedTypes = Tipo [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

parece que a chamada "getClass" não funcionou corretamente. Alguma sugestão...?

Edit2: verifiquei no Guia do Usuário do Gson . Ele menciona uma exceção de tempo de execução que deve ocorrer durante a análise de um tipo genérico para o Json. Fiz isso "errado" (não mostrado acima), como no exemplo, mas não recebi a exceção. Então mudei a serialização como no guia do usuário sugerido. Não ajudou, no entanto.

Edit3: Resolvido, veja minha resposta abaixo.

medusa
fonte
1
A resposta que você apontou, usa TokenType. Você já tentou dessa maneira?
Nishant
só tenho a mesma dica que uma resposta. da próxima vez, examinarei mais detalhadamente o exemplo. ;)
medusa
Você pode tentar uma implementação da lista no tipo token? Como seu tipo bruto é lista de matrizes, você deve tentar a lista de matrizes.
Uncaught_exceptions

Respostas:

954

Método para desserializar a coleção genérica:

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

Como várias pessoas nos comentários mencionaram, aqui está uma explicação de como a TypeTokenclasse está sendo usada. A construção new TypeToken<...>() {}.getType()captura um tipo de tempo de compilação (entre <e >) em um java.lang.reflect.Typeobjeto de tempo de execução . Ao contrário de um Classobjeto, que pode representar apenas um tipo bruto (apagado), o Typeobjeto pode representar qualquer tipo na linguagem Java, incluindo uma instanciação parametrizada de um tipo genérico.

A TypeTokenclasse em si não tem um construtor público, porque você não deve construí-lo diretamente. Em vez disso, você sempre constrói uma subclasse anônima (daí a {}, que é uma parte necessária dessa expressão).

Devido ao apagamento do tipo, a TypeTokenclasse só pode capturar tipos que são totalmente conhecidos no momento da compilação. (Ou seja, você não pode fazer new TypeToken<List<T>>() {}.getType()por um parâmetro de tipo T.)

Para mais informações, consulte a documentação da TypeTokenturma .

uncaught_exceptions
fonte
31
Nas novas versões do GSON, o construtor TypeToken não é público; portanto, aqui você obtém um erro de construtor não visível. O que você tem que fazer neste caso?
194 Pablo Pablo
8
Usando a versão atual do GSON (2.2.4), ele funciona novamente. Você pode acessar o construtor aqui.
meu objeto json começa com um objeto e, em seguida, contém uma matriz do objeto que eu quero { "myObjectArray":[ {....} , {....} , {....} ] }, criei o arquivo de modelo {....}, como faço para obter esse código genérico de coleção para não assumir que meu elemento raiz é uma matriz sem criar um novo arquivo de objeto aninhado
CQM
7
A seguir Importações necessárias --- import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem
4
Isso é bom se YourClass estiver corrigido no código. E se a classe vier em tempo de execução?
jasxir
273

Outra maneira é usar uma matriz como um tipo, por exemplo:

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

Dessa forma, você evita todos os problemas com o objeto Type e, se realmente precisa de uma lista, pode sempre converter a matriz em uma lista:

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

IMHO isso é muito mais legível.

E para torná-la uma lista real (que pode ser modificada, consulte as limitações de Arrays.asList()), faça o seguinte:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
DevNG
fonte
4
isso é ótimo! Como posso usá-lo com reflexão? Não sei o MyClassvalor e ele será definido dinamicamente!
Amin Sh
1
nota: com isso, tenha cuidado para que o mcList não seja uma lista completa. muitas coisas não vão funcionar.
Njzk2
4
Como usá-lo com genéricos? T[] yourClassList = gson.fromJson(message, T[].class);// não pode selecionar a partir de tipo de variável
Pawel Cioch
2
@MateusViccari no momento desse comentário, mcListnesta resposta, foi apenas o resultado da chamada para Arrays.asList. Esse método retorna uma lista na qual a maioria, se não todos, os métodos opcionais são deixados sem implementação e lançam exceções. Por exemplo, você não pode adicionar nenhum elemento a essa lista. Como sugere a edição posterior, Arrays.asListpossui limitações, e agrupá-lo em um número real ArrayListpermite obter uma lista que é mais útil em muitos casos.
Njzk2 6/0317
2
Se você precisar construir um tipo de matriz em tempo de execução para um tipo de elemento arbitrário, poderá usar Array.newInstance(clazz, 0).getClass()como descrito na resposta de David Wood .
Daniel Pryden
31

Consulte este post. Tipo Java genérico como argumento para GSON

Eu tenho uma solução melhor para isso. Aqui está a classe de wrapper para lista, para que o wrapper possa armazenar exatamente o tipo de lista.

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

E então, o código pode ser simples:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}
Mais feliz
fonte
O que é mEntity.rulePattern?
Al Lelopath
É apenas um objeto de amostra para teste. Você não precisa se preocupar com isso. Use o método toList e tudo vai bem.
feliz
@Happier Estou tentando implementar este Gson 2.8.2 e parece não estar funcionando. Alguma chance stackoverflow.com/questions/50743932/... você pode dar uma olhada e deixe-me saber o que estou ausente
Praveen
1
@Praveen Eu tentei dessa maneira no 2.8.2, ele funciona como original.
feliz 18/06
31

Desde então Gson 2.8, podemos criar a função util como

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

Exemplo usando

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);
Phan Van Linh
fonte
2
TypeToken#getParameterizedparece uma maneira melhor, então o corte com uma subclasse anônima
Nikolay Kulachenko
essa deve ser a resposta aceita; pelo menos para versões mais recentes
ccpizza
2
Esta é a resposta perfeita. Resolve meu problema.
9139 donmj
Copiei seu método "como está" e ele não funciona: o compilador diz "O método getParameterized (Classe <List>, Classe <T>) é indefinido para o tipo TypeToken". Eu verifiquei minha versão do Gson (2.8.0) e a documentação e está tudo bem deste lado ... acabei usando a solução @Happier que funciona bem
leguminator
@ iluminador você importou TypeToken correto? e você está usando java ou kotlin. Vou tentar testar novamente
Phan Van Linh
26

Wep, outra maneira de alcançar o mesmo resultado. Nós o usamos para sua legibilidade.

Em vez de fazer esta frase difícil de ler:

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

Crie uma classe vazia que estenda uma lista do seu objeto:

public class YourClassList extends ArrayList<YourClass> {}

E use-o ao analisar o JSON:

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

Para Kotlin simplesmente:

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

ou, aqui está uma função útil:

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

Então, para usar:

val type = typeOfList<YourMagicObject>()
Chad Bingham
fonte
Eu usei o código para criar esta função usando tipos reificadas: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!e chamá-lo com o tipo List:buildType<List<YourMagicObject>>()
coffeemakr
@ coffeemakr Você não precisa de tipos reificados aqui.
Chad Bingham
Oh. Mas por que você cria o token de tipo de um ArrayList buildTypee também chama a função com o tipo genérico? Isso é um erro de digitação? - Isso criaria ArrayList <ArrayList <YourMagicObject>>
coffeemakr
ah café @ ah, sim. 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);
}

Exemplo:

getList(MyClass[].class, "[{...}]");
kayz1
fonte
Um bom. Mas isso duplica DevNGa resposta acima, escrita 2 anos antes: stackoverflow.com/a/17300003/1339923 (e leia a resposta para ressalvas a essa abordagem) #
11353
6

Como ele responde à minha pergunta original, aceitei a resposta do doc_180, mas se alguém encontrar esse problema novamente, também responderei à segunda metade da minha pergunta:

O NullPointerError que descrevi não teve nada a ver com a própria lista, mas com seu conteúdo!

A classe "MyClass" não tinha um construtor "no args" e nem a superclasse. Depois de adicionar um construtor "MyClass ()" simples ao MyClass e sua superclasse, tudo funcionou bem, incluindo a serialização e desserialização da lista, conforme sugerido por doc_180.

medusa
fonte
1
Se você tiver uma lista de classes abstratas, receberá o mesmo erro. Eu acho que essa é a mensagem de erro geral do GSON para "Impossível instanciar classe".
Drew
A dica sobre como adicionar um construtor me ajudou a entender por que eu tinha todos os valores nulos. Eu tinha nomes de campos como "Para" e "De" na minha string JSON, mas os campos correspondentes no meu objeto eram "para" e "de" em letras minúsculas, portanto foram ignorados
Rune
4

Aqui está uma solução que funciona com um tipo definido dinamicamente. O truque é criar o tipo adequado de matriz usando 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
fonte
Essa resposta seria melhor se você class.cast()evitasse o aviso não verificado causado pela transmissão para (T). Ou, melhor ainda, não se preocupe com a conversão e use algo como Arrays.asList()converter de uma matriz para a List<T>. Além disso, não há necessidade de passar um comprimento para Array.newInstance()- uma matriz de tamanho zero será suficiente para chamar getClass().
Daniel Pryden
Obrigado pela sugestão, fiz as alterações sugeridas e atualizei a postagem acima.
David Wood
2

Quero acrescentar mais uma possibilidade. Se você não deseja usar o TypeToken e deseja converter a matriz de objetos json em um ArrayList, pode proceder da seguinte maneira:

Se sua estrutura json é como:

{

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

}

e sua estrutura de classe é como:

public class ClassName implements Parcelable {

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

então você pode analisá-lo como:

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

Agora você pode acessar cada elemento do objeto className.

Apurva Sharma
fonte
1

Consulte o exemplo 2 para entender a classe 'Tipo' do Gson.

Exemplo 1: Neste deserilizeResturant, usamos a matriz Employee [] e obtemos os detalhes

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

Exemplo 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
fonte
0

Gostei da resposta do kays1, mas não consegui implementá-la. Então eu construí minha própria versão usando o conceito dele.

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

Uso:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
mike83_dev
fonte
Certamente isso não pode funcionar, pois você está tentando usar T em tempo de compilação. Isso efetivamente desserializará para uma Lista de StringMap, não?
JHH
0

No meu caso, a resposta de @ uncaught_exceptions não funcionou, eu tive que usar em List.classvez de java.lang.reflect.Type:

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