JSON e instalar kotlinx.serialization
Al terminar: Entender qué es JSON y instalar el plugin de serialización y la librería kotlinx.serialization paso a paso.
Marcador ya calcula tablas, goleadores y errores, pero todo desaparece al cerrar el programa. Para guardarlo en un fichero hace falta JSON, un formato de texto para datos, y una librería que convierta tus objetos a ese texto y de vuelta: kotlinx.serialization. En esta lección verás qué es JSON, qué es una librería y qué es un plugin, y las instalarás paso a paso. Cómo usarlas lo ves en la lección siguiente, y el torneo se guardará en la que viene después, Guardado.kt en Marcador.
Al terminar, :torneo tendrá el plugin y la librería instalados, y ColorDeTarjeta llevará la anotación @Serializable. Temas: la sintaxis de JSON; librería y plugin de compilador; kotlinx.serialization; instalarla en Gradle.
JSON: datos como texto
JSON es una forma de escribir datos como texto plano, con unas pocas reglas, de modo que un programa los guarde en un fichero o se los envíe a otro. Resuelve un problema: la memoria de un programa (objetos, listas) desaparece al cerrarlo y no se puede copiar tal cual a otro sitio, pero un texto sí.
Todo JSON se construye con unas pocas piezas:
{
"nombre": "Panadería Sol",
"abierta": true,
"empleados": 3,
"dueño": null,
"productos": [
{ "nombre": "Pan", "precio": 120 },
{ "nombre": "Leche", "precio": 95 }
]
}| Pieza | Qué significa |
|---|---|
{ … } | Un objeto: un grupo de pares clave-valor |
"clave": valor | Un campo (un par clave-valor): el nombre, entre comillas dobles, y su valor, separados por dos puntos |
"Panadería Sol" | Un texto: siempre entre comillas dobles |
3 | Un número: sin comillas |
true / false | Un booleano: sin comillas |
null | «No hay valor» |
[ … ] | Una lista: valores separados por comas (aquí, dos objetos) |
Los campos de un objeto se separan con comas, y el último no lleva coma detrás. Los espacios y saltos de línea no cambian el significado: {"nombre":"Pan"} es el mismo JSON que uno escrito en varias líneas.
Un objeto de JSON no es un objeto de Kotlin: es solo un grupo de pares clave-valor, sin funciones ni clases. Y un campo de JSON es uno de esos pares, no una propiedad de Kotlin.
Librería y plugin: dos cosas distintas
- Una librería es código ya escrito por otras personas que añades a tu proyecto para usarlo, como una caja de herramientas que alguien más preparó. Se añade con una línea en
dependencies { … }. - Un plugin de compilador es una pieza que se mete dentro del compilador (el programa que convierte tu código en algo ejecutable) y, mientras compila, escribe código nuevo por ti. Se añade en el bloque
plugins { … }.
La serialización necesita las dos a la vez, como verás justo debajo.
Serialización: de objetos a texto y de vuelta
kotlinx.serialization son dos piezas, y hacen falta las dos:
| Pieza | Qué es | Qué hace |
|---|---|---|
| El plugin de compilador | Se aplica en Gradle | Al compilar, mira cada clase marcada con @Serializable y genera el código que la convierte a JSON y de vuelta |
La librería kotlinx-serialization-json | Una dependencia | Trae Json, encodeToString, decodeFromString y la propia anotación |
Sobre lo que hace la anotación, la documentación oficial dice: «Esta anotación indica al compilador que genere el código necesario para serializar y deserializar instancias de la clase.» (una instancia es un objeto creado desde esa clase).
Si quitas cada pieza por separado, esto es lo que ocurre:
- Sin el plugin: compila sin quejarse y falla al ejecutar. En Marcador el fallo queda dentro del
Resulty se imprime así:No se pudo guardar: Serializer for class 'TorneoGuardado' is not found.(TorneoGuardadoes una clase que crearás en Guardado.kt en Marcador) seguido dePlease ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. - Sin la librería: ni compila. Los errores son
Unresolved reference 'kotlinx'.yIllegal annotation class 'Serializable'.
El primero es el peligroso: sin plugin, el programa arranca y el error solo se ve al guardar. Y si ignoras el Result (como viste en Result: errores como valores), ni eso.
El ejemplo más pequeño de serialización
Antes de instalar nada, mira qué se consigue con las dos piezas puestas. Este ejemplo es solo para leer: no es de Marcador, y no funcionaría en tu proyecto hasta el paso 9 de esta lección. Solo enseña qué hacen @Serializable y Json:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable // le pide al plugin que prepare la conversión de esta clase
data class Plato(val nombre: String, val precio: Int)
fun main() {
val sopa = Plato("Sopa", 120)
// de objeto a texto JSON
val texto = Json.encodeToString(sopa)
println(texto)
// de texto JSON a objeto: <Plato> dice qué tipo reconstruir
val vuelta = Json.decodeFromString<Plato>("""{"nombre":"Curry","precio":150}""")
println(vuelta)
println(vuelta.precio + 50)
}Al ejecutarlo, imprime:
{"nombre":"Sopa","precio":120}
Plato(nombre=Curry, precio=150)
200Json.encodeToString(objeto)(«codificar a texto») devuelve el JSON de ese objeto.Json.decodeFromString<Tipo>(texto)(«decodificar desde texto») lee el JSON y crea el objeto; el tipo entre<>le dice qué clase construir.- Las tres comillas
"""del texto de arriba son otra forma de escribir un texto: dentro se pueden poner comillas dobles sin más. - El objeto que vuelve es un
Platode verdad: se puede usarvuelta.precioy sumar.
Reto · Ejercicio: lee y escribe JSON a mano
La primera parte es de lápiz y la segunda, de Playground; no necesita proyecto ni librería, basta con la tabla de piezas de arriba. Este es el JSON de una cafetería:
{
"nombre": "Café Luna",
"abierto": false,
"mesas": 4,
"menu": [
{ "plato": "Sopa", "precio": 120 },
{ "plato": "Curry", "precio": 150 }
]
}- Contesta, mirando solo el texto: ¿cuánto vale
mesas? ¿Qué valeabierto? ¿Cuántos elementos tiene la listamenu? ¿Cuánto vale elpreciodel segundo? - Escribe a mano, en el mismo formato, el JSON de otra cafetería:
nombre«Café Sol»,abiertoverdadero, 2 mesas y un único plato en el menú, «Té» a 80. Luego pásalo al Kotlin Playground dentro deprintln(""" … """)(las tres comillas, como en el ejemplo de arriba) y comprueba que se imprime tal cual lo escribiste.
Ver solución
mesasvale4(un número, sin comillas).abiertovalefalse(un booleano).menutiene 2 elementos (dos objetos entre corchetes). Elpreciodel segundo es150.- Uno posible (los espacios y los saltos de línea no importan):
{
"nombre": "Café Sol",
"abierto": true,
"mesas": 2,
"menu": [
{ "plato": "Té", "precio": 80 }
]
}Las comprobaciones habituales: el texto lleva comillas dobles, los números y true no llevan comillas, la lista va entre corchetes aunque tenga un solo elemento y no hay coma detrás del último campo.
Paso · Instala el plugin de serialización y la librería JSON
Objetivo: que :torneo tenga el plugin y la librería de kotlinx.serialization, y que Serializable se reconozca en el código.
Es la receta de Los ficheros de Gradle de tu proyecto: si no recuerdas qué son el catálogo, alias, implementation o Sync Now, repásala antes de seguir.
1 · Qué instalas. El plugin org.jetbrains.kotlin.plugin.serialization se mete en el compilador y escribe por ti el código que pasa cada clase @Serializable a JSON y de vuelta. La librería kotlinx-serialization-json trae Json, encodeToString y decodeFromString. Guardar el torneo en un fichero vive en :torneo, así que se instala ahí.
2 · [versions]. Abre libs.versions.toml (Version Catalog "libs") y añade al final de [versions]:
kotlinxSerializationJson = "1.11.0" # su número propio. El plugin no añade versión: reutiliza «kotlin»3 · [libraries]. Al final de la sección:
# group:name = coordenadas; version.ref apunta a la línea de [versions]
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }4 · [plugins]. Al final de la sección:
# Un plugin de compilador debe tener la versión del compilador: version.ref = "kotlin"
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }5 · La raíz, con apply false. En build.gradle.kts (Project: Marcador) añade una línea a plugins. apply false es dejar el material en el almacén: el plugin queda disponible, pero la raíz no lo activa porque no compila nada.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
}plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
// alias del catálogo con el guion hecho punto; sin «apply false», la raíz lo aplicaría y fallaría
alias(libs.plugins.kotlin.serialization) apply false
}6 · El módulo, sin apply false. En build.gradle.kts (Module :torneo), línea nueva en plugins, debajo del de Kotlin y encima de application:
plugins {
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.kotlin.serialization) // aquí sí se activa; después del de Kotlin, en que se apoya
application
}7 · La dependencia. En el mismo fichero, encima de testImplementation:
dependencies {
implementation(libs.kotlinx.serialization.json) // implementation: la usa el programa (Guardado.kt)
testImplementation(libs.kotlin.test)
}8 · Sync Now. Al editar un fichero de Gradle aparece una barra arriba del editor con el botón Sync Now: pulsa ese botón para que Android Studio relea los ficheros de Gradle y descargue lo nuevo. Sin errores rojos; la primera vez necesita internet.
9 · Comprueba. Ponle la anotación @Serializable al enum ColorDeTarjeta: es la única clase de las de ahora que irá tal cual en el fichero, y sirve para comprobar que la instalación funciona; las demás clases se anotan cuando se creen, en Guardado.kt en Marcador. Abre EventoDePartido.kt (en torneo → kotlin+java → com.sebastiannarvaez.marcador.torneo). Debajo de package, deja una línea en blanco y escribe el import; encima de enum class ColorDeTarjeta, la anotación. Ninguna debe salir en rojo (si kotlinx sale subrayado, repite Sync). Después, ./gradlew :torneo:run debe terminar en BUILD SUCCESSFUL con la misma salida de antes.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
EventoDePartido.kt, el import (debajo de package):
import kotlinx.serialization.SerializableEventoDePartido.kt, la anotación, encima de enum class ColorDeTarjeta:
@SerializableLa prueba definitiva del plugin llega en Guardado.kt en Marcador, al guardar el primer torneo: sin él, el programa compila y falla al ejecutar.
Punto de control
- En la Terminal de Android Studio,
./gradlew :torneo:test :torneo:runtermina enBUILD SUCCESSFUL, con la misma salida de antes. - Abre el informe de tests (
open torneo/build/reports/tests/test/index.html, como en Bucles y tu primer test): arriba pone 75 tests, ninguno nuevo. - En
Gradle Scripts, el catálogo tienekotlinxSerializationJson = "1.11.0",kotlinx-serialization-jsonen[libraries]ykotlin-serializationen[plugins];build.gradle.kts (Project: Marcador)lo declara conapply false, ybuild.gradle.kts (Module :torneo)lo aplica y tieneimplementation(libs.kotlinx.serialization.json). EventoDePartido.kttiene elimporty@Serializableencima deenum class ColorDeTarjeta, sin rojo.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.¿Cuál de estas líneas es JSON válido?
2.Compilas Marcador con la librería
kotlinx-serialization-jsonpero sin aplicar el plugin de serialización. ¿Qué ocurre?3.En JSON, ¿qué es un «objeto»?
4.Al añadir el plugin de serialización al catálogo, ¿qué
version.reflleva y por qué?5.Sobre
apply falseenbuild.gradle.kts (Project: Marcador)y la líneaalias(…)debuild.gradle.kts (Module :torneo), ¿cuál es verdadera?