How to use TypeToken + generics with Gson in Kotli

2019-01-10 09:16发布

问题:

I'm unable to get a List of generic type from a custom class (Turns):

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

it said:

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

回答1:

Create this inline fun:

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

and then you can call it in this way:

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

NOTE: This approach was not possible before in old kotlin plugin versions but now you are able to use it.


Previous Alternatives:

ALTERNATIVE 1:

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

You have to put object : and the specific type in fromJson<List<Turns>>


ALTERNATIVE 2:

As @cypressious mention it can be achieved also in this way:

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

use as:

val turnsType = genericType<List<Turns>>()


回答2:

This solves the problem:

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

The first line creates an object expression that descends from TypeToken and then gets the Java Type from that. Then the Gson().fromJson method either needs the type specified for the result of the function (which should match the TypeToken created). Two versions of this work, as above or:

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

To make it easier to create the TypeToken you can create a helper function, which is required to be inline so that it can use reified type parameters:

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

Which can then be used in either of these ways:

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

And the whole process can be wrapped into an extension function for the Gson instance:

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

So that you can just call Gson and not worry about the TypeToken at all:

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

Here Kotlin is using type inference from one side of the assignment or the other, and reified generics for an inline function to pass through the full type (without erasure), and using that to construct a TypeToken and also make the call to Gson



回答3:

Another option (not sure it looks more elegant than the others) might be a call like this:

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

So you are using the java Array class one liner instead of "pure Kotlin".



回答4:

val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

It's my way to parsing data array in kotlin.



回答5:

This works as well, and is simpler

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


回答6:

I used something like this to convert T to string & String back to T using Gson. Not exactly what you are looking for but just in case.

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