Fonaments
La serialització ens permet transformar unes dades en un "string".
Introducció
Section titled “Introducció”La serialització de dades no és tan fàcil com pot semblar al principi, perquè la majoria de les dades que consumim o produïm no s’ajusten exactament a la definició de les nostres classes.
Projecte: https://gitlab.com/xtec/kotlin/serialization
Entorn de treball
Section titled “Entorn de treball”Crea un projecte serial
amb Amper.
Kotlin Serialization és la biblioteca oficial multiplataforma i multiformat de serialització per a Kotlin.
Si necessites (de)serialitzar classes Kotlin des de/cap a JSON, pots habilitar Kotlin Serialization en la seva forma més simple:
settings: kotlin: serialization: json # JSON or other format
Aquest fragment configura el compilador per processar classes @Serializable, i afegeix dependències al temps d’execució de serialització i a les biblioteques de format JSON.
També pots personalitzar la versió de les biblioteques de Kotlin Serialization utilitzant la forma completa de la configuració:
settings: kotlin: serialization: format: json version: 1.7.3
Modifica el fitxer module.yaml
:
product: jvm/appsettings: kotlin: serialization: json
Serialitzar
Section titled “Serialitzar”En la nostra aplicació la informació està objectes Data.
Per guardar una dada primer l’has de convertir en un String
.
La manera més habitual és convertir una dada a JSON
Has d’anotar la data class
com @Serializable
perquè el “plugin” de serialitzador del compilador generi el mètode serializer()
.
El mètode serializer()
descompon l’objecte en un conjunt de valor primitius:
import kotlinx.serialization.*
@Serializabledata class Person(val name: String, val married: Boolean)
fun main() { println(Person.serializer().descriptor)}
Executa l’aplicació:
.\amper run
La propietat descriptor
del serialitzador mostra que la classe Person
està formada pels elements primitius name
de tipus kotlin.String
i married
de tipus kotlin.Boolean
.
Person(name: kotlin.String, married: kotlin.Boolean)
Si mires el fitxer Person.class
que s’ha generat dins de la carpeta build
pots veure que s’ha generat el codi de Person.serializer()
de manera automàtica:
A continuació, codifiquem el conjunt de valors primitius en un format concret mitjançant un codificador.
En el nostre cas, utilitzem un codificador JSON:
import kotlinx.serialization.*import kotlinx.serialization.json.Json
@Serializabledata class Person(val name: String, val married: Boolean)
fun main() { val mary = Person("Mary", true) println(mary)
val json = Json.encodeToString(mary) println(json)}
Per convertir un objecte a format JSON has d’utilitzar la funció d’extensió Json.encodeToString
, que serialitza l’objecte que es passa com a paràmetre i el codifica en un string JSON.
Person(name=Mary, married=true){"name":"Mary","married":true}
Fixa’t que en la representació JSON no es guarda informació de la classe, en aquest cast Person
.
Activitat
Section titled “Activitat”Tenim aquest diagrama de dades:
1. Crea les data class
corresponents.
Recorda que Address
també s’ha de marcar com @Serializable
.
2. Crea algunes dades demo
3.- Serialitza les dades demo a format Json:
Referències
Section titled “Referències”Quan serialitzem dades no estem guardant dades en una base de dades.
Referències repetides
Section titled “Referències repetides”No hi ha referències, hi ha redundància
Que vol dir que pot haber dades repetides perquè un objecte pot fer referència al mateix objecte de manera directa o indirecta.
Per exemple, en un projecte l’owner
i el mantainer
són del tipus User
:
@Serializableclass Project(val name: String, val owner: User, val maintainer: User)
@Serializableclass User(val name: String)
I poden ser la mateixa persona:
@Serializableclass Project(val name: String, val owner: User, val maintainer: User)
@Serializableclass User(val name: String)
fun main() { val david = User("David") val data = Project("Quantum System", david, david) println(Json.encodeToString(data))}
El procés de serialització no té en compte que són referències al mateix objecte:
{ "name": "Quantum System", "owner": { "name":"david" }, "maintainer":{"name":"david"}}
És per aquest motiu que les bases de dades documentals com MongoDB tenen dades redundants.
Referències circulars
Section titled “Referències circulars”Com ja hem explicat abans, la serialització no té en compte si un objecte ja s’havia serialitzat.
Això vol dir que a més de la redundància, la serialització només funciona amb arbres: un objecte arrel, ramificacions i tota acaba en fulles.
Es comença amb un objecte i es van processant de manera recursiva totes les propietats serialitzables que són referències fins que no queda cap objecte referenciat per processar.
Per exemple, si David
té una referència directa o indirecta amb Esther
, i Esther
té una referència directa o indirecta amb David
, la recursió no acabarà mai 💫💫💫💫💫 … llevat que 🙀🙀??
Vam explicar a Data que quan representem dades totes les propietats són val
.
D’aquesta manera és impossible crear referències circulars!!
Pots veure que en aquest exemple, la sentència esther.partner = david
no compila:
@Serializableclass Person(val name: String, val partner: Person? = null)
fun main() {
val esther = Person("Esther") val david = Person("David", esther) esther.partner = david}
El disseny correcte és aquest:
@Serializableclass Person(val name: String)
@Serializableclass Partners(val a: Person, val b: Person)
fun main() {
val esther = Person("Esther") val david = Person("David") println(Json.encodeToString(Partners(esther, david))) {"a":{"name":"Esther"},"b":{"name":"David"}}}
La serialització no acaba mai …
El que acaba és el programa amb un “Stack Overflow Error”.
Deserialitzar
Section titled “Deserialitzar”Per convertir un string JSON en un objecte has d’utilitzar la funció d’extensió Json.decodeFromString
.
Aquesta funció, a més de l’String
, espera un paràmere de tipus per saber a quina classe ha de descodificar perquè l’string JSON no té aquesta informació,
Tens dos opcions:
// Passar el parametre de tipus a la funcióval project1 = Json.decodeFromString<Project>(data)
// Que la variable tingui un tipus explícitval project2: Project = Json.decodeFromString(data)
A continuació tens un exemple:
import kotlinx.serialization.*import kotlinx.serialization.json.*
@Serializabledata class Project(val name: String, val language: String)
fun main() { val data = """{"name":"Quantum System","language":"Borg"}"""
val project = Json.decodeFromString<Project>(data) require(project == Project("Quantum System", "Borg"))}
Si executes aquest codi pots verificar que a partir d’un string pots crear un objecte Project
amb el contingut de l’string.
I molt important, la funció només espera una classe “serialitzable” que tingui una estructura a la qual pugui descodificar perquè té una estructura identica a Project
.
A continuació tens un exemple d’una classe que no és Project
i “consumeix” les mateixes dades:
import kotlinx.serialization.*import kotlinx.serialization.json.*
@Serializabledata class Person(val name: String, val language: String)
fun main() { val data = """{"name":"Raquel Durán","language":"Català"}"""
val raquel = Json.decodeFromString<Person>(data) require(raquel == Person("Raquel Durán", "Català"))}
El motiu és que serialitzes dades per comunicar-te amb altres aplicacions i serveis, i cada aplicació fa servir el nom de la classe que més li convé:
import kotlinx.serialization.*import kotlinx.serialization.json.*
fun main() { @Serializable data class Kangaroo(val name: String)
@Serializable data class Person(val name: String)
val json = """{"name":"toby"}"""
// Creem un cangur a partir de json val kangaroo = Json.decodeFromString<Kangaroo>(json)
// Creem una persona a partir del mateix json val person = Json.decodeFromString<Person>(json)
// Verifiquem que kangaroo i person son objectes diferents require(kangaroo != person)
// però que tenen el mateix nom require(kangaroo.name == person.name)}
En alguna aplicació “Toby” pot ser un 🧒 i en una altra pot ser un 🦘.
Activitat
Section titled “Activitat”En l’activitat “Sky” de Data vas dissenyar un sistema de gestió de vols:
Creua un vol i el converteixes en un “string”:
import kotlinx.serialization.*import kotlinx.serialization.json.Json
@Serializabledata class Plane(val id: Long, val name: String, val seats: Int)
@Serializabledata class Airport(val id: Long, val name: String, val country: String)
@Serializabledata class Passenger(val id: Long, val name: String)
@Serializabledata class Flight( val id: Long, val ufoSeen: Boolean, val plane: Plane, val departure: Airport, val arrival: Airport, val passengers: List<Passenger>)
@Serializableclass Project(val name: String, val owner: User, val maintainer: User)
@Serializableclass User(val name: String)
fun main() {
val flight = Flight( 23, false, Plane(45, "Ostrich", 40), Airport(1, "London", "UK"), Airport(2, "Madrid", "Spain"), listOf(Passenger(1, "David"), Passenger(2, "Alice")) )
val pretty = Json { prettyPrint = true } println(pretty.encodeToString(flight))}
El contingut d'aquest lloc web té llicència CC BY-NC-ND 4.0.
©2022-2025 xtec.dev