Jak używać typów generycznych TypeToken + z Gson w Kotlin

108

Nie mogę uzyskać listy typu ogólnego z klasy niestandardowej (obroty):

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

powiedziało:

cannot access '<init>' it is 'public /*package*/' in 'TypeToken'
Juan Saravia
źródło

Odpowiedzi:

237

Utwórz tę wbudowaną zabawę:

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

a potem możesz to nazwać w ten sposób:

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

Poprzednie alternatywy:

ALTERNATYWA 1:

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

Musisz umieścić object :i konkretny typfromJson<List<Turns>>


ALTERNATYWA 2:

Jak wspomina @cypressious, można to osiągnąć również w ten sposób:

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

Użyj jako:

val turnsType = genericType<List<Turns>>()
Juan Saravia
źródło
4
Możesz także stworzyć pomocniczą metodę, która zrobi to za Ciebie:inline fun <reified T> genericType() = object: TypeToken<T>() {}.type
Kirill Rakhman,
1
lub nawet rozszerza Gson o nowe przeciążenie fromJson, które to robi. Kotlin jest zaprojektowany do rozszerzania, więc rozszerz Gson, aby był ładniejszy i ukryj TypeTokens.
Jayson Minard
Dokonałem sugerowanej zmiany, która sprawia, że ​​odpowiedź jest bardziej kompletna i formalna, ponieważ ta odpowiedź jest prawdopodobnie widziana przez wielu użytkowników Gson. Dodałem wyjaśnienia w odpowiedzi i linki do odniesień Kotlin do tematów używanych do rozwiązania problemu ... aby ludzie nie musieli czytać wszystkich innych odpowiedzi lub komentarzy, które weszły w to. Jeśli zaakceptujesz zmianę, mogę usunąć moją odpowiedź poniżej.
Jayson Minard
Edycja odrzucona, zobacz moją odpowiedź poniżej, aby uzyskać pełną wersję, która łączy wszystkie odpowiedzi i komentarze w jedną spójną odpowiedź. Przyjąłeś własną odpowiedź, ale nie jest kompletna.
Jayson Minard
usuwanie ostrzeżenia kotlin: inline fun <reified T> genericType (): Type? = obiekt: TypeToken <T> () {} .type
Juan Mendez
28

To rozwiązuje problem:

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

Pierwsza linia tworzy wyrażenie obiektowe, które pochodzi od, TypeTokena następnie pobiera z niego Javę Type. Następnie Gson().fromJsonmetoda wymaga albo typu określonego dla wyniku funkcji (który powinien pasować do TypeTokenutworzonej). Dwie wersje tej pracy, jak wyżej lub:

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

Aby ułatwić tworzenie TypeToken, możesz utworzyć funkcję pomocniczą, która musi być wbudowana, aby mogła używać parametrów typu reified :

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

Które można następnie wykorzystać na jeden z następujących sposobów:

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

Cały proces można opakować w funkcję rozszerzającą dla Gsoninstancji:

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

Abyś mógł po prostu zadzwonić do Gson i wcale się nie martwić TypeToken:

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

Tutaj Kotlin używa wnioskowania o typie z jednej lub drugiej strony przypisania i reified generics dla funkcji inline, która przechodzi przez pełny typ (bez wymazywania) i używa tego do skonstruowania a, TypeTokena także wywołania Gson

Jayson Minard
źródło
1
Cześć @Jayson, nie mogę sprawić, by działało to tak fajnie w Android Studio. Wydaje się, że jest w porządku, ale nie jest rozpoznawany, kiedy to robięGson().fromJson<kotlin.List<Turns>>(pref.turns)
Juan Saravia
@juancho, czy możesz mi powiedzieć, co oznacza „nierozpoznany”? Błąd kompilatora? Masz metodę rozszerzenia zaimportowaną i dostępną z góry?
Jayson Minard
2
Kopiuję i wklejam twój kod w Android Studio i zaimportowałem zabawę do mojej klasy kotlin. Próbowałem zrobić to, co powiedziałeś, ale z jakiegoś powodu kompilator powiedział mi, że taka zabawa nie istnieje. Używam już innych funkcji rozszerzających, ale nie wiem, co Twoja sugestia nie działa. Której wersji AS i Kotlin używasz? po prostu spróbować ponownie.
Juan Saravia
Nie jest to bezpośrednio związane ze studiem Android, Kotlin jest taki sam wewnątrz lub na zewnątrz. Tworzysz instancję, Gson()czy po prostu Gsontak jakby była statyczna? Potrzebujesz pierwszej instancji.
Jayson Minard
21

Inną opcją (nie jestem pewien, czy wygląda bardziej elegancko niż inne) może być takie połączenie:

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

Więc używasz java Array class one liner zamiast "czystego Kotlin".

Tobias Reich
źródło
2
Ponieważ TypeToken nie działa na każdym telefonie niezawodnie, jest to dla mnie najlepsze rozwiązanie. Łatwy jeden liner z czystym kotlinem.
LuckyMalaka
11
val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

To mój sposób na parsowanie tablicy danych w kotlin.

Toàn Mỹ
źródło
To powinna być przyjęta odpowiedź, krótka i prosta.
Umar Ata
6

Kiedyś coś takiego do konwersji Tdo stringi Stringz powrotem do Tkorzystania Gson. Nie do końca to, czego szukasz, ale na wszelki wypadek.

Deklarowanie rozszerzenia

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)

Stosowanie

// 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
źródło
5

To również działa i jest prostsze

    inline fun <reified T> Gson.fromJson(json: String) : T = 
         this.fromJson<T>(json, T::class.java)
Christopher Perry
źródło
Zwracany typ musi mieć wartość null. W przeciwnym razie kod Java w bibliotece Gson może zwrócić wartość null, ale Kotlin zakłada, że ​​typ nie dopuszcza wartości null. W rezultacie otrzymujesz NPE w Kotlinie.
fdermishin
1

Kotlin generic reified functionz Gson deserializacji, aby ArrayList<T>użyć tego kodu

 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
}
Muzułmanin Shahsavan
źródło