Cómo usar TypeToken + genéricos con Gson en Kotlin

108

No puedo obtener una lista de tipo genérico de una clase personalizada (turnos):

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

decía:

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

Respuestas:

236

Crea esta diversión en línea:

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

y luego puedes llamarlo de esta manera:

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)

Tienes que poner object :y el tipo específico enfromJson<List<Turns>>


ALTERNATIVA 2:

Como mencionan @cypressious, también se puede lograr de esta manera:

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

usar como:

val turnsType = genericType<List<Turns>>()
Juan Saravia
fuente
4
También puede crear un método auxiliar que lo haga por usted:inline fun <reified T> genericType() = object: TypeToken<T>() {}.type
Kirill Rakhman
1
o incluso extiende Gson para tener una nueva sobrecarga de fromJson que hace esto. Kotlin está diseñado para extenderse, así que extienda Gson para hacerlo más agradable y oculte TypeTokens.
Jayson Minard
Hice una edición sugerida que hace que la respuesta sea más completa y formal, ya que es probable que muchos de los que usan Gson vean esta respuesta. Agregué explicaciones en la respuesta y enlaces a las referencias de Kotlin para los temas utilizados para resolver el problema ... para que la gente no tenga que leer todas las demás respuestas o comentarios que se incluyeron en esto. Si acepta la edición, puedo eliminar mi respuesta a continuación.
Jayson Minard
Edite rechazado, vea mi respuesta a continuación para obtener una versión completa que combina todas las respuestas y comentarios en una respuesta coherente. Aceptaste tu propia respuesta pero no está completa.
Jayson Minard
eliminación de la advertencia de kotlin: diversión en línea <T reificada> genericType (): Type? = objeto: TypeToken <T> () {} .type
Juan Mendez
28

Esto resuelve el problema:

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

La primera línea crea una expresión de objeto que desciende TypeTokeny luego obtiene Java Typede eso. Entonces el Gson().fromJsonmétodo necesita el tipo especificado para el resultado de la función (que debe coincidir con el TypeTokencreado). Dos versiones de este trabajo, como arriba o:

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

Para facilitar la creación TypeToken, puede crear una función auxiliar, que debe estar en línea para que pueda usar parámetros de tipo reificado :

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

Que luego se puede usar de cualquiera de estas formas:

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

Y todo el proceso se puede envolver en una función de extensión para la Gsoninstancia:

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

Para que puedas llamar a Gson y no preocuparte por TypeTokennada:

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

Aquí Kotlin está usando la inferencia de tipos de un lado de la asignación o del otro, y genéricos reificados para que una función en línea pase a través del tipo completo (sin borrado), y lo usa para construir un TypeTokeny también hacer la llamada a Gson

Jayson Minard
fuente
1
Hola @Jayson, no pude hacer que funcione tan divertido en línea en Android Studio. Parece estar bien, pero no se reconoce cuando lo hagoGson().fromJson<kotlin.List<Turns>>(pref.turns)
Juan Saravia
@juancho ¿puedes decirme qué significa "no reconocido"? ¿Un error del compilador? ¿Tiene el método de extensión importado y disponible desde arriba?
Jayson Minard
2
Copio y pego tu código en Android Studio e importé la diversión en mi clase de kotlin. Intenté hacer lo que dijiste, pero por alguna razón el compilador me dijo que esta diversión no existe. Ya estoy usando otras funciones de extensión, pero no sé qué no funciona tu sugerencia. ¿Qué versión de AS y Kotlin estás usando? solo para intentarlo de nuevo.
Juan Saravia
Esto no está relacionado directamente con el estudio de Android, Kotlin es el mismo dentro o fuera de eso. ¿Está creando una instancia de Gson()o simplemente Gsoncomo si fuera estática? Necesitas el primero, una instancia.
Jayson Minard
21

Otra opción (no estoy seguro de que se vea más elegante que las otras) podría ser una llamada como esta:

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

Entonces estás usando el trazador de líneas de clase uno de Java Array en lugar de "Kotlin puro".

Tobias Reich
fuente
2
Dado que TypeToken no funciona en todos los teléfonos confiables, esta es la mejor solución para mí. Un trazador de líneas sencillo con puro kotlin.
LuckyMalaka
11
val obj: MutableList<SaleItemResponse> = Gson().fromJson(messageAfterDecrypt,
    object : TypeToken<List<SaleItemResponse>>() {}.type)

Es mi forma de analizar la matriz de datos en kotlin.

Toàn Mỹ
fuente
Esta debe ser la respuesta aceptada, breve y sencilla.
Umar Ata
6

Solía algo como esto para convertir Ta stringy Stringvolver a Tutilizar Gson. No es exactamente lo que estás buscando, pero por si acaso.

Declaración de extensión

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
fuente
5

Esto también funciona y es más sencillo

    inline fun <reified T> Gson.fromJson(json: String) : T = 
         this.fromJson<T>(json, T::class.java)
Christopher Perry
fuente
El tipo de retorno debe ser anulable. De lo contrario, el código Java en la biblioteca Gson puede devolver un valor nulo, pero Kotlin asume que el tipo no admite valores nulos. Como resultado, obtienes NPE en Kotlin.
fdermishin
1

Kotlin generic reified functionde Gson deserializar 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 musulmán
fuente