Comment utiliser les génériques TypeToken + avec Gson dans Kotlin

108

Je ne parviens pas à obtenir une liste de type générique à partir d'une classe personnalisée (tours):

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

Ça disait:

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

Réponses:

237

Créez ce plaisir en ligne:

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

et ensuite vous pouvez l'appeler de cette façon:

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

Alternatives précédentes:

ALTERNATIVE 1:

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

Vous devez mettre object :et le type spécifique dansfromJson<List<Turns>>


ALTERNATIVE 2:

Comme @cypressious le mentionne, cela peut également être réalisé de cette manière:

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

utilisé comme:

val turnsType = genericType<List<Turns>>()
Juan Saravia
la source
4
Vous pouvez également créer une méthode d'aide qui le fait pour vous:inline fun <reified T> genericType() = object: TypeToken<T>() {}.type
Kirill Rakhman
1
ou même étend Gson pour avoir une nouvelle surcharge de fromJson qui fait cela. Kotlin est conçu pour s'étendre, alors étendez Gson pour le rendre plus agréable et masquer les TypeTokens.
Jayson Minard
J'ai fait une modification suggérée qui rend la réponse plus complète et formelle, car cette réponse est susceptible d'être vue par de nombreux utilisateurs de Gson. J'ai ajouté des explications dans la réponse et des liens vers des références Kotlin pour les sujets utilisés pour résoudre le problème ... afin que les gens n'aient pas à lire toutes les autres réponses ou commentaires qui y sont liés. Si vous acceptez la modification, je peux supprimer ma réponse ci-dessous.
Jayson Minard
Modification rejetée, voir ma réponse ci-dessous pour une version complète qui combine toutes les réponses et commentaires en une seule réponse cohérente. Vous avez accepté votre propre réponse, mais elle n'est pas complète.
Jayson Minard
suppression de l'avertissement de kotlin: fun en ligne <reified T> genericType (): Type? = object: TypeToken <T> () {} .type
Juan Mendez
28

Cela résout le problème:

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

La première ligne crée une expression d'objet qui descend de TypeTokenpuis obtient le Java à Typepartir de cela. Ensuite, la Gson().fromJsonméthode a besoin du type spécifié pour le résultat de la fonction (qui doit correspondre à celui TypeTokencréé). Deux versions de ce travail, comme ci-dessus ou:

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

Pour faciliter la création du, TypeTokenvous pouvez créer une fonction d'assistance, qui doit être en ligne pour pouvoir utiliser des paramètres de type réifiés :

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

Qui peut ensuite être utilisé de l'une de ces manières:

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

Et l'ensemble du processus peut être enveloppé dans une fonction d'extension pour l' Gsoninstance:

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

Pour que vous puissiez simplement appeler Gson et ne pas vous inquiéter du TypeTokentout:

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

Ici, Kotlin utilise l'inférence de type d'un côté de l'affectation ou de l'autre, et des génériques réifiés pour qu'une fonction en ligne passe par le type complet (sans effacement), et l'utilise pour construire un TypeTokenet également faire l'appel à Gson

Jayson Minard
la source
1
Salut @Jayson, je ne pourrais pas le faire fonctionner aussi amusant en ligne dans Android Studio. Ça semble aller mais ce n'est pas reconnu quand je le faisGson().fromJson<kotlin.List<Turns>>(pref.turns)
Juan Saravia
@juancho pouvez-vous me dire ce que signifie «non reconnu»? Une erreur de compilation? Vous avez la méthode d'extension importée et disponible d'en haut?
Jayson Minard
2
Je copie et colle votre code dans Android Studio et ai importé le plaisir dans ma classe kotlin. J'ai essayé de faire ce que vous avez dit mais pour une raison quelconque, le compilateur m'a dit que ce plaisir n'existe pas. J'utilise déjà d'autres fonctions d'extention mais je ne sais pas ce que votre suggestion ne fonctionne pas. Quelle version d'AS et de Kotlin utilisez-vous? juste pour essayer à nouveau.
Juan Saravia
Ce n'est pas directement lié au studio Android, Kotlin est le même à l'intérieur ou à l'extérieur de cela. Créez-vous une instance de Gson()ou simplement Gsoncomme si elle était statique? Vous avez besoin du premier, une instance.
Jayson Minard le
21

Une autre option (pas sûr qu'elle ait l'air plus élégante que les autres) pourrait être un appel comme celui-ci:

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

Vous utilisez donc la doublure java Array class one au lieu de "pure Kotlin".

Tobias Reich
la source
2
Étant donné que TypeToken ne fonctionne pas sur tous les téléphones fiables, c'est la meilleure solution pour moi. Une doublure simple avec du kotlin pur.
LuckyMalaka
11
val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

C'est ma façon d'analyser le tableau de données dans kotlin.

Toàn Mỹ
la source
Telle devrait être la réponse acceptée, courte et simple.
Umar Ata le
6

J'ai utilisé quelque chose comme ça pour convertir Tà stringet Stringrevenir à l' Tutilisation Gson. Pas exactement ce que vous recherchez mais juste au cas où.

Déclaration d'extension

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)

Usage

// 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
        }
    }
dur_v
la source
5

Cela fonctionne également et est plus simple

    inline fun <reified T> Gson.fromJson(json: String) : T = 
         this.fromJson<T>(json, T::class.java)
Christopher Perry
la source
Le type de retour doit être nullable. Sinon, le code Java de la bibliothèque Gson peut retourner null, mais Kotlin suppose que le type est non nullable. En conséquence, vous obtenez NPE à Kotlin.
fdermishin
1

Kotlin generic reified functionde Gson désérialise pour ArrayList<T>utiliser ce code

 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
}
Musulman Shahsavan
la source