Saltar al contenido
K
Kotlin: el lenguajePaso a paso35 min de lectura · 5 min de máquina Básico

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:

ejemplo mínimo: la sintaxis de JSON (explicado debajo)
{
  "nombre": "Panadería Sol",
  "abierta": true,
  "empleados": 3,
  "dueño": null,
  "productos": [
    { "nombre": "Pan", "precio": 120 },
    { "nombre": "Leche", "precio": 95 }
  ]
}
PiezaQué significa
{ … }Un objeto: un grupo de pares clave-valor
"clave": valorUn 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
3Un número: sin comillas
true / falseUn 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:

PiezaQué esQué hace
El plugin de compiladorSe aplica en GradleAl 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-jsonUna dependenciaTrae 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 Result y se imprime así: No se pudo guardar: Serializer for class 'TorneoGuardado' is not found. (TorneoGuardado es una clase que crearás en Guardado.kt en Marcador) seguido de Please 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'. y Illegal 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:

ejemplo mínimo: de objeto a JSON y de vuelta (con comentarios)
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)
200
  • Json.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 Plato de verdad: se puede usar vuelta.precio y 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:

el JSON del ejercicio
{
  "nombre": "Café Luna",
  "abierto": false,
  "mesas": 4,
  "menu": [
    { "plato": "Sopa", "precio": 120 },
    { "plato": "Curry", "precio": 150 }
  ]
}
  1. Contesta, mirando solo el texto: ¿cuánto vale mesas? ¿Qué vale abierto? ¿Cuántos elementos tiene la lista menu? ¿Cuánto vale el precio del segundo?
  2. Escribe a mano, en el mismo formato, el JSON de otra cafetería: nombre «Café Sol», abierto verdadero, 2 mesas y un único plato en el menú, «Té» a 80. Luego pásalo al Kotlin Playground dentro de println(""" … """) (las tres comillas, como en el ejemplo de arriba) y comprueba que se imprime tal cual lo escribiste.
Ver solución
  1. mesas vale 4 (un número, sin comillas). abierto vale false (un booleano). menu tiene 2 elementos (dos objetos entre corchetes). El precio del segundo es 150.
  2. Uno posible (los espacios y los saltos de línea no importan):
solución del reto
{
  "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.

Gradle Scripts
libs.versions.toml (Version Catalog "libs")se modifica: [versions], [libraries] y [plugins]
build.gradle.kts (Project: Marcador)se modifica: plugins, con apply false
build.gradle.kts (Module :torneo)se modifica: plugins y dependencies
torneo → kotlin+java → com.sebastiannarvaez.marcador.torneo
EventoDePartido.ktse modifica: import y una anotación
Los tres ficheros de esta instalación, tal como los ves en la vista Android.

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]:

catálogo: línea nueva en [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:

catálogo: línea nueva en [libraries]
# 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:

catálogo: línea nueva en [plugins]
# 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.

build.gradle.kts (Project: Marcador): antes
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.compose) apply false
    alias(libs.plugins.jetbrains.kotlin.jvm) apply false
}
build.gradle.kts (Project: Marcador): después (con comentarios)
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:

build.gradle.kts (Module :torneo): el bloque plugins, después (con comentarios)
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:

build.gradle.kts (Module :torneo): el bloque dependencies, después (con comentarios)
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):

torneo/src/main/java/com/sebastiannarvaez/marcador/torneo/EventoDePartido.kt
import kotlinx.serialization.Serializable

EventoDePartido.kt, la anotación, encima de enum class ColorDeTarjeta:

torneo/src/main/java/com/sebastiannarvaez/marcador/torneo/EventoDePartido.kt
@Serializable

La 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:run termina en BUILD 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 tiene kotlinxSerializationJson = "1.11.0", kotlinx-serialization-json en [libraries] y kotlin-serialization en [plugins]; build.gradle.kts (Project: Marcador) lo declara con apply false, y build.gradle.kts (Module :torneo) lo aplica y tiene implementation(libs.kotlinx.serialization.json).
  • EventoDePartido.kt tiene el import y @Serializable encima de enum class ColorDeTarjeta, sin rojo.

Comprueba lo que entendiste

5 preguntas · necesitas 4 para completar la lección

  1. 1.¿Cuál de estas líneas es JSON válido?

  2. 2.Compilas Marcador con la librería kotlinx-serialization-json pero sin aplicar el plugin de serialización. ¿Qué ocurre?

  3. 3.En JSON, ¿qué es un «objeto»?

  4. 4.Al añadir el plugin de serialización al catálogo, ¿qué version.ref lleva y por qué?

  5. 5.Sobre apply false en build.gradle.kts (Project: Marcador) y la línea alias(…) de build.gradle.kts (Module :torneo), ¿cuál es verdadera?

Faltan 5 de 5