Verwendung von TypeToken + Generika mit Gson in Kotlin

106

Ich kann keine Liste mit generischen Typen aus einer benutzerdefinierten Klasse (Turns) abrufen:

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

es sagte:

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

Antworten:

233

Erstellen Sie diesen Inline-Spaß:

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

und dann können Sie es so nennen:

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

Bisherige Alternativen:

ALTERNATIVE 1:

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

Sie müssen eingeben object :und den spezifischen Typ eingebenfromJson<List<Turns>>


ALTERNATIVE 2:

Wie @cypressious erwähnt, kann dies auch auf folgende Weise erreicht werden:

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

benutzen als:

val turnsType = genericType<List<Turns>>()
Juan Saravia
quelle
3
Sie können auch eine inline fun <reified T> genericType() = object: TypeToken<T>() {}.type
Hilfsmethode
1
oder erweitert Gson sogar um eine neue Überladung von fromJson, die dies tut. Kotlin wurde entwickelt, um zu erweitern, also erweitern Sie Gson, um es schöner zu machen und TypeTokens zu verstecken.
Jayson Minard
Ich habe eine vorgeschlagene Änderung vorgenommen, die die Antwort vollständiger und formeller macht, da diese Antwort wahrscheinlich von vielen gesehen wird, die Gson verwenden. Ich habe der Antwort Erklärungen und Links zu Kotlin-Referenzen für Themen hinzugefügt, die zur Lösung des Problems verwendet wurden. Die Leute müssen also nicht alle anderen Antworten oder Kommentare lesen, die dazu geführt haben. Wenn Sie die Bearbeitung akzeptieren, kann ich meine Antwort unten entfernen.
Jayson Minard
Bearbeiten abgelehnt, siehe meine Antwort unten für eine Vollversion, die alle Antworten und Kommentare in einer zusammenhängenden Antwort kombiniert. Sie haben Ihre eigene Antwort akzeptiert, aber sie ist nicht vollständig.
Jayson Minard
Kotlin-Warnung entfernen: Inline-Spaß <reified T> genericType (): Typ? = Objekt: TypeToken <T> () {} .type
Juan Mendez
28

Dies löst das Problem:

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

Die erste Zeile erstellt einen Objektausdruck , der von diesem abstammt TypeTokenund dann Java Typeabruft. Dann benötigt die Gson().fromJsonMethode entweder den für das Ergebnis der Funktion angegebenen Typ (der mit dem TypeTokenerstellten übereinstimmen sollte ). Zwei Versionen dieser Arbeit, wie oben oder:

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

Um das Erstellen der zu vereinfachen, TypeTokenkönnen Sie eine Hilfsfunktion erstellen, die inline sein muss, damit sie Parameter mit geändertem Typ verwenden kann :

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

Was dann auf eine der folgenden Arten verwendet werden kann:

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

Und der gesamte Prozess kann in eine Erweiterungsfunktion für die GsonInstanz eingebunden werden :

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

Damit Sie einfach Gson anrufen können und sich überhaupt keine Sorgen machen müssen TypeToken:

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

Hier verwendet Kotlin die Typinferenz von der einen oder der anderen Seite der Zuweisung und reifizierte Generika für eine Inline-Funktion, um den vollständigen Typ (ohne Löschung) zu durchlaufen, und verwendet diese, um einen zu konstruieren TypeTokenund auch Gson aufzurufen

Jayson Minard
quelle
1
Hallo @Jayson, ich konnte nicht dafür sorgen, dass dieser Inline-Spaß in Android Studio funktioniert. Scheint in Ordnung zu sein, aber es wird nicht erkannt, wenn ich es tueGson().fromJson<kotlin.List<Turns>>(pref.turns)
Juan Saravia
@juancho kannst du mich anrufen was "nicht erkannt" bedeutet? Ein Compilerfehler? Sie haben die Erweiterungsmethode importiert und sind von oben verfügbar?
Jayson Minard
2
Ich kopiere und füge deinen Code in Android Studio ein und importiere den Spaß in meine Kotlin-Klasse. Ich habe versucht, das zu tun, was Sie gesagt haben, aber aus irgendeinem Grund hat mir der Compiler gesagt, dass dieser Spaß nicht existiert. Ich verwende bereits andere Erweiterungsfunktionen, weiß aber nicht, was Ihr Vorschlag nicht funktioniert. Welche Version von AS und Kotlin verwenden Sie? nur um es noch einmal zu versuchen.
Juan Saravia
Dies hängt nicht direkt mit Android Studio zusammen, Kotlin ist innerhalb oder außerhalb davon gleich. Erstellen Sie eine Instanz von Gson()oder nur so, Gsonals ob sie statisch wäre? Sie benötigen die erste, eine Instanz.
Jayson Minard
21

Eine andere Option (nicht sicher, ob sie eleganter aussieht als die anderen) könnte ein Anruf wie dieser sein:

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

Sie verwenden also den Java Array Class One Liner anstelle von "pure Kotlin".

Tobias Reich
quelle
2
Da TypeToken nicht auf jedem Telefon zuverlässig funktioniert, ist dies die beste Lösung für mich. Ein einfacher Einzeiler mit reinem Kotlin.
LuckyMalaka
11
val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

Es ist meine Art, Datenarrays in Kotlin zu analysieren.

Toàn Mỹ
quelle
Dies sollte die akzeptierte Antwort sein, kurz und einfach.
Umar Ata
6

Ich habe so etwas verwendet, um Tzu string& Stringzurück zu Tverwenden Gson. Nicht genau das, wonach Sie suchen, aber nur für den Fall.

Verlängerung erklären

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)

Verwendung

// 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
        }
    }
hart_v
quelle
5

Dies funktioniert auch und ist einfacher

    inline fun <reified T> Gson.fromJson(json: String) : T = 
         this.fromJson<T>(json, T::class.java)
Christopher Perry
quelle
Der Rückgabetyp muss nullwertfähig sein. Andernfalls kann Java-Code in der Gson-Bibliothek null zurückgeben, Kotlin geht jedoch davon aus, dass der Typ nicht nullwertfähig ist. Als Ergebnis erhalten Sie NPE in Kotlin.
Fdermishin
1

Kotlin generic reified functionvon Gson deserialisiert, ArrayList<T>um diesen Code zu verwenden

 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
}
Moslemischer Shahsavan
quelle