Como usar TypeToken + genéricos com Gson em Kotlin

108

Não consigo obter uma lista de tipo genérico de uma classe personalizada (Turns):

val turnsType = TypeToken<List<Turns>>() {}.type
val turns = Gson().fromJson(pref.turns, turnsType)

disse:

cannot access '<init>' it is 'public /*package*/' in 'TypeToken'
Juan Saravia
fonte

Respostas:

236

Crie esta diversão inline:

inline fun <reified T> Gson.fromJson(json: String) = fromJson<T>(json, object: TypeToken<T>() {}.type)

e então você pode chamá-lo desta forma:

val turns = Gson().fromJson<Turns>(pref.turns)
// or
val turns: Turns = Gson().fromJson(pref.turns)

Alternativas anteriores:

ALTERNATIVA 1:

val turnsType = object : TypeToken<List<Turns>>() {}.type
val turns = Gson().fromJson<List<Turns>>(pref.turns, turnsType)

Você tem que colocar object :e o tipo específico emfromJson<List<Turns>>


ALTERNATIVA 2:

Como menção @cypressious, também pode ser alcançado desta forma:

inline fun <reified T> genericType() = object: TypeToken<T>() {}.type

usar como:

val turnsType = genericType<List<Turns>>()
Juan Saravia
fonte
4
Você também pode criar um método auxiliar que faça isso para você:inline fun <reified T> genericType() = object: TypeToken<T>() {}.type
Kirill Rakhman,
1
ou até mesmo estende Gson para ter uma nova sobrecarga de fromJson que faz isso. Kotlin foi projetado para estender, então estenda Gson para torná-lo mais agradável e ocultar TypeTokens.
Jayson Minard
Fiz uma sugestão de edição que torna a resposta mais completa e formal, uma vez que essa resposta provavelmente será vista por muitos que usam Gson. Eu adicionei explicações na resposta e links para referências do Kotlin para tópicos usados ​​para resolver o problema ... para que as pessoas não precisem ler todas as outras respostas ou comentários que entraram nisso. Se você aceitar a edição, posso remover minha resposta abaixo.
Jayson Minard
Editar rejeitado, veja minha resposta abaixo para uma versão completa que combina todas as respostas e comentários em uma resposta coerente. Você aceitou sua própria resposta, mas ela não está completa.
Jayson Minard
removendo aviso de kotlin: fun inline <reified T> genericType (): Type? = objeto: TypeToken <T> () {} .type
Juan Mendez
28

Isso resolve o problema:

val turnsType = object : TypeToken<List<Turns>>() {}.type
val turns = Gson().fromJson<List<Turns>>(pref.turns, turnsType)

A primeira linha cria uma expressão de objeto que desce TypeTokene obtém o Java a Typepartir dela . Então, o Gson().fromJsonmétodo precisa do tipo especificado para o resultado da função (que deve corresponder ao TypeTokencriado). Duas versões deste trabalho, como acima ou:

val turns: List<Turns> = Gson().fromJson(pref.turns, turnsType)

Para facilitar a criação de, TypeTokenvocê pode criar uma função auxiliar, que deve estar embutida para que possa usar parâmetros de tipo reificado :

inline fun <reified T> genericType() = object: TypeToken<T>() {}.type

Que pode então ser usado de uma destas maneiras:

val turnsType = genericType<List<Turns>>()
// or
val turnsType: List<Turns> = genericType()

E todo o processo pode ser envolvido em uma função de extensão para a Gsoninstância:

inline fun <reified T> Gson.fromJson(json: String) = this.fromJson<T>(json, object: TypeToken<T>() {}.type)

Para que você possa apenas ligar para Gson e não se preocupar com TypeTokennada:

val turns = Gson().fromJson<Turns>(pref.turns)
// or
val turns: Turns = Gson().fromJson(pref.turns)

Aqui, Kotlin está usando inferência de tipo de um lado da atribuição ou do outro, e genéricos reificados para que uma função inline passe pelo tipo completo (sem apagamento) e usando isso para construir um TypeTokene também fazer a chamada para Gson

Jayson Minard
fonte
1
Olá @Jayson, não consegui fazer com que funcionasse com tanta diversão embutida no Android Studio. Parece estar bem, mas não é reconhecido quando eu façoGson().fromJson<kotlin.List<Turns>>(pref.turns)
Juan Saravia
@juancho, você pode me dizer o que significa "não reconhecido"? Um erro do compilador? Você tem o método de extensão importado e disponível acima?
Jayson Minard
2
Copiei e colei seu código no Android Studio e importei a diversão para minha aula de kotlin. Tentei fazer o que você disse, mas por algum motivo o compilador me disse que essa diversão não existe. Já estou usando outras funções de extensão, mas não sei o que sua sugestão não funciona. Qual versão do AS e Kotlin você está usando? apenas para tentar novamente.
Juan Saravia
Isso não está relacionado diretamente ao Android Studio, Kotlin é o mesmo dentro ou fora dele. Você está criando instância de Gson()ou apenas Gsoncomo se fosse estático? Você precisa do primeiro, uma instância.
Jayson Minard
21

Outra opção (não tenho certeza se parece mais elegante do que as outras) pode ser uma chamada como esta:

turns = Gson().fromJson(allPurchasesString, Array<Turns>::class.java).toMutableList()

Portanto, você está usando o liner de classe um do java Array em vez de "Kotlin puro".

Tobias Reich
fonte
2
Como o TypeToken não funciona em todos os telefones confiáveis, esta é a melhor solução para mim. Um forro fácil com kotlin puro.
LuckyMalaka
11
val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

É a minha maneira de analisar o array de dados em kotlin.

Toàn Mỹ
fonte
Esta deve ser a resposta aceita, curta e simples.
Umar Ata
6

Eu usei algo como isto para converter Tpara stringe Stringvolta a Tutilizar Gson. Não exatamente o que você está procurando, mas apenas no caso.

Declarando extensão

inline fun <reified T : Any> T.json(): String = Gson().toJson(this, T::class.java)
inline fun <reified T : Any> String.fromJson(): T = Gson().fromJson(this,T::class.java)

Uso

// Passing an object to new Fragment
companion object {    
        private const val ARG_SHOP = "arg-shop"

        @JvmStatic
        fun newInstance(shop: Shop) =
                ShopInfoFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_SHOP, shop.json())
                    }
                }
    }

// Parsing the passed argument
private lateinit var shop: Shop

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            shop = it.getString(ARG_SHOP).fromJson() ?: return
        }
    }
harsh_v
fonte
5

Isso também funciona e é mais simples

    inline fun <reified T> Gson.fromJson(json: String) : T = 
         this.fromJson<T>(json, T::class.java)
Christopher Perry
fonte
O tipo de retorno deve ser anulável. Caso contrário, o código Java na biblioteca Gson pode retornar nulo, mas Kotlin assume que o tipo não pode ser nulo. Como resultado, você obtém NPE em Kotlin.
fdermishin
1

Kotlin generic reified functionde Gson desserializa para ArrayList<T>usar este código

 inline fun <reified T> get( ... ): ArrayList<T>{
    
    val str = "[{},{}]"
    
    val type = TypeToken.getParameterized(ArrayList::class.java, T::class.java).type
    
    val t = Gson().fromJson<ArrayList<T>>(str, type)
    

    return t
}
Shahsavan muçulmano
fonte