Tots el tipus primitius i algunes classes de la llibreria estàndard de Kotlin es serialitzen per defecte.
Introducció
A continuació anem a veure tots els tipus que es serialitzen per defecte.
Primitives
La serialització de Kotlin té aquest deu "primitives": Boolean
, Byte
, Short
, Int
, Long
, Float
, Double
, Char
, String
, i enumeracions.
Els tipus Boolean
, Char
i String
es poden utilitzar sense cap tipus de consideració al respecte, però els altres necessiten algunes aclaracions.
Números
Kotlin té 4 tipus per representar números enters i 2 tipus per representar números en coma flotat, i com hem dit abans, tots són serialitzables.
@Serializable
class Data(
val answer: Int,
val pi: Double
)
fun main() {
val data = Data(42, PI)
println(Json.encodeToString(data))
}
Pots veure que aquests números es codifiquen sense problemes en JSON:
{
"answer": 42,
"pi": 3.141592653589793
}
Codificar un Long
Hi ha llenguatges de programació que tenen més tipus, i altres menys, però el que ens interessa en el procés de serialització és el tipus que pot representar el format al qual codifiquem les dades.
JSON utilitza els tipus de Javascript, i Javascritpt només té el tipus number
que equival a un Double
.
Ja saps que qualsevol valor de tipus Byte
, Short
, Int
o Float
es pot representar amb un Double
, peró no tots els valors de tipus Long
es poden representar amb un Double
.
Per defecte, un Long
amb un valor molt gran es codifica tal qual:
TODO els dos literals coincidents
@Serializable
class Data(val signature: Long)
fun main() {
val data = Data(0x1CAFE2FEED0BABE0)
println(Json.encodeToString(data))
}
Pot veure que signature
es codifica com un número encara que un number
(o un Double
) no pugui representar aquest número:
{ "signature": 2067120338512882656 }
Això no representa cap problema sempre i quant aquest número no el decodifiquin mètodes natius Javascript (Kotlin/JS no té problemes).
En aquests casos, pots anotar una propietat amb LongAsStringSerializer per representar el número amb un string i no perdre precisió.
@Serializable
class Data(
@Serializable(with=LongAsStringSerializer::class)
val signature: Long
)
fun main() {
val data = Data(0x1CAFE2FEED0BABE0)
println(Json.encodeToString(data))
}
Pots veure que ara el valor de signature
no és un number
sinó un string
:
{ "signature": "2067120338512882656" }
Descodificar números
El motiu d'utilizar tipus com Byte
, Short
, Int
o Float
en processadors de 64 bits es estalviar espai en memòria principal o secundària.
Encara que un valor tingui un tipus Byte
aquest valor és processa en la CPU igual que si fos de tipus Long
: el temps de processament és el mateix.
I en dades d'intercanvi, sobretot quan has de descodificar dades, utilitzar aquests tipus no aporta cap benefici i molts problemes.
El dos únics tipus que has d'utlitzar per llegir dades JSON són Int
i Float
.
TODO: demostra el problema , classe amb short
, etc. i ...
Enum classes
En principi, totes les classes d'enumeració es poden serialitzar sense haver de marcar-les amb l'anotació @Serializable.
A continuació tens un exemple:
enum class Status { CLOSED, SUPPORTED }
@Serializable
class Project(val name: String, val status: Status)
fun main() {
val data = Project("dev.xtec.data", Status.SUPPORTED)
println(Json.encodeToString(data))
}
En JSON, una enumeració es codifica amb un string amb el nom del discriminador de l'enum
:
{ "name": "dev.xtec.data", "status": "SUPPORTED" }
Però hi ha dues exepcions!
1.- A Kotlin/JS i Kotlin/Native has de marcar la classe enum
amb l'anotació @Serializable
si la vols utilitzar com a objecte arrel en el procés de serialització.
encodeToString<Status>(Status.SUPPORTED).
2.- Vols canviar el nom que es fa sevir pel discriminador de l'enum quan es serialitza amb l'anotació `@SerialName.
En aquest cas també has d'anotar la classe amb @Serializable
:
@Serializable
enum class Status { CLOSED, @SerialName("maintained") SUPPORTED }
@Serializable
class Project(val name: String, val status: Status)
fun main() {
val data = Project("dev.xtec.data", Status.SUPPORTED)
println(Json.encodeToString(data))
}
Pots veure que ara el valor de status
és "maintained" enlloc de "SUPPORTED":
{ "name":"dev.xtec.data", "status": "maintained" }
Objectes compostos
En Kotlin, els tipus primaris boolean
, byte
, etc. són objectes simples.
Els objectes compostos estan formats per objectes simple o compostos de manera recursiva definits per la classe corresponent.
Nomes les classes de la biblioteca estàndard de Kotlin que veurem a continuació es poden serialitzar per defecte.
Pair i triple
Les classes de dades simples Pair i Triple de la biblioteca estàndard de Kotlin són serialitzables.
@Serializable
class Project(val name: String)
fun main() {
val pair = 1 to Project("dev.xtec.data")
println(Json.encodeToString(pair))
}
Pots veure que un objecte de tipus Pair
es serialitza com un objecte json amb dos propietats: first
i second
.
{ "first": 1, "second": { "name":"dev.xtec.data"} }
Col.leccions
Totes les col.leccions en que els seus elements es poden serialitzar es poden serialitzar.
Codificar
Totes les col.leccions, execepte Map
, es representen amb un "array" JSON.
Per exemple, una List
en que tots els seus elements són serialitzables es pot serialitzar.
@Serializable
class Project(val name: String)
fun main() {
val list = listOf(
Project("kotlinx.serialization"),
Project("kotlinx.coroutines")
)
println(Json.encodeToString(list))
}
Tal com hem explicat al principi, la llista es representa com un array JSON:
[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}]
Pots veure que un Set
també es poden serialitzar:
@Serializable
class Project(val name: String)
fun main() {
val set = setOf(
Project("kotlinx.serialization"),
Project("kotlinx.coroutines")
)
println(Json.encodeToString(set))
}
I el Set
també es representa com un array JSON:
[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}]
Decodificar
JSON només té el tipus array
i totes les col.leccions es codifiquen com arrays.
Per tant, al decodificar un string JSON el tipus de la col.leció el determina el tipus de la propietat de la classe o el paràmetre de tipus de la funció de descodificació (TODO posar exemple).
A continuació tens un exemple en que es mostra com el mateix array JSON es deserialitza en dues propietats amb tipus diferent:
@Serializable
data class Data(
val a: List<Int>,
val b: Set<Int>
)
fun main() {
val data = Json.decodeFromString<Data>("""
{
"a": [42, 42],
"b": [42, 42]
}
""")
println(data)
}
Com que la propietat data.b
és un Set
, els valors duplicats no apareixen:
Data(a=[42, 42], b=[42])
Map
Un Map
amb claus primitives o enumeracions, i valors serialitzables es pot serialitzar
@Serializable
class Project(val name: String)
fun main() {
val map = mapOf(
1 to Project("kotlinx.serialization"),
2 to Project("kotlinx.coroutines")
)
println(Json.encodeToString(map))
}
A diferència del reste de col.leccions, un Map
es codifica com un objecte JSON (un objecte JSON equival a un map).
L'única diferència és que les claus sempre es codifiquen en string tal com pots veure en el resultat:
{
"1": { "name": "kotlinx.serialization" },
"2": { "name": "kotlinx.coroutines" }
}
Duration
La classe Duration
també és serialitzable:
fun main() {
val duration = 1000.toDuration(DurationUnit.SECONDS)
println(Json.encodeToString(duration))
}
La durada es serialitza com una string en el format ISO-8601-2:
"PT16M40S"
Objectes Unit i singletons
El tipus Unit
també es serialitzable.
Unit
és un singleton object i es gestiona igual que els altres objectes.
Conceptualment, un singleton és una classe amb només una instància, el que significa que l'estat no defineix l'objecte, però l'objecte defineix el seu estat.
A JSON, els objectes es serialitzen com a estructures buides:
@Serializable
object SerializationVersion {
val libraryVersion: String = "1.0.0"
}
fun main() {
println(Json.encodeToString(SerializationVersion))
println(Json.encodeToString(Unit))
}
Tot i que pot semblar inútil a primera vista, això és útil per a la serialització de classes "sealed" com explicarem a TODO Polymorphism.Objects:
{}
{}
Nothing
Per defecte, Nothing
és una classe serialitzable.
Tanmateix, com que no hi ha instàncies d'aquesta classe, és impossible codificar o descodificar els seus valors; qualsevol intent provocarà una excepció.
Aquest serialitzador s'utilitza quan sintàcticament es necessita algun tipus, però en realitat no s'utilitza en la serialització.
Per exemple, quan s'utilitzen classes base polimòrfiques parametritzades:
@Serializable
sealed class ParametrizedParent<out R> {
@Serializable
data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
}
fun main() {
println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
}
```
Quan es codifica, el serialitzador per `Nothing` no s'ha utilitzat:
```json
{ "value": 42 }
```