La funció d'un serialitzador es determinar com es descomposa un objecte en les seves propietats constituents.

Introducció

Cada format de codificació representa un objecte d'una manera específic: XML i JSON poden representar el mateix objecte, però ho fan de manera diferent.

La funció d'un serialitzador es determinar com es descomposa un objecte en les seves propietats constituents.

Fins ara hem utilitzat:

  1. Serialitzadors que es deriven de manera automàtica a partir de l'anotació @Serializable

  2. Serialitzadors que ja estan implementat per les classes bàsiques.

Per exemple, a continuació tenim una classe Color que te un propietat rgb de tipus Int per guardar el codi del color:

@Serializable
class Color(val rgb: Int)

fun main() {
    val green = Color(0x00ff00)
    println(Json.encodeToString(green))
}  

Per defecte, aquesta classe serialitza la propietat rgb a JSON mitjnaçant:

  1. Un serialitzador derivat a partir de la classe Color
  2. Un serialitzador ja implementat per a la classe Int
{ "rgb": 65280 }

I ara anem ha explicar com es fa.

Serialitzador

Cada tipus concret necessita un serialitzador específic, que és un classe que implementa la interfície [KSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html.

.serializer()

Quan marques una classe amb l'anotació @Serializable, com has fet amb la classe Color, el plugin del compilador genera de manera automàtica una classe que implementa la interfice KSerializer amb el codi necessari per serialitzar aquesta, i només aquesta classe.

Com és lògic, el serializador quan ha de serialitzar un objecte necessita un objecte que implementi la classe que s'ha generat de manera automàtica.

Però enlloc de crear un objecte cada cop que ha de serialitzar un objecte, cosa que seria una autèntica burrada en termes de rendiment, el plugin del compilador també crea un objecte complementari de la classe que has anotat amb @Serializable.

L'objecte complementari té la funció .serializer() que retorna una única instància de la classe que implementa la interfície KSerializerque s'ha generat automàticament.

De la mateixa que el serialitzador accedeix a aquesta instància de manera automàtica, tu també pots, ja que es pot utilitzar de manera concurrent sense problemes:

fun main() {
    val colorSerializer: KSerializer<Color> = Color.serializer()
    println(colorSerializer.descriptor)
}

La instància de tipus KSerializer té la propietat descriptor que descriu l'estrucutra de la classe serialitzada:

Color(rgb: kotlin.Int)

Tipus primitiu

També pots obtenir una referència del serialitzados de les classes primàries amb l'extensió .serializer():

fun main() {
    val serializer: KSerializer<Int> = Int.serializer()
    println(serializer.descriptor)
}

Tipus genèric

En el cas d'una classe genèrica, la funció .serializer() necessita que li passis com argument el serialitzador (una instància de la classe KSerializer) que ha d'utilitzar per a un instància concreta.

En aquest exemple pots veure que al cridar la funció serialzer() de l'objecte complemenari de la classe genèrica Box<T> has de passar com argument el serialitzador que obtens de l'objecte complementari de la classe Color:

@Serializable           
class Box<T>(val contents: T)    

fun main() {
    val serializer = Box.serializer(Color.serializer())
    println(serializer.descriptor)
}

Per poder serialitzar la classe concreta Box<Color>, has creat un serialitzador específic per a aquesta classe:

Box(contents: Color)

Col.leccions

Cada col.lecció té una serialitzador concret: ListSerializer(), SetSerializer(), MapSerializer(), etc.

A més, com que les col.leccions són genèriques, necessiten com a paramètre un serialitzador igual que la classe Box<T> com has vista abans.

Per exemple, podem produir un serialitzador per a List<String>:

fun main() {   
    val serializer: KSerializer<List<String>> = ListSerializer(String.serializer()) 
    println(serializer.descriptor)
}

En cas de dubte, sempre pots utilitzar la funció genèrica d'alt nivell `serializer<T>()` per obtenir el serialitzador de qualsevol tipus quan escrius codi:

```kt
@Serializable            
class Color(val rgb: Int)

fun main() {        
    val serializer: KSerializer<Map<String, Color>> = serializer()
    println(serializer.descriptor)
}

Esciure un serialitzador

A vegades el serialitzador que es genera de manera automàtica amb l'anotació @Serializable no genera el JSON que ens agradaria.

Serialitzador primari

Si vols serialitzar pots serialitzar direcament un objecte a un tipus primari.

Per exemple, el serialitzador de Color representa el valor amb un número mitjançant el serialitzador autogenerat per Colori el serialitzador Int.serialitzer().

Imagina que enlloc d'un número vols que el valor del color es representi amb un string hexadecimal, per exemple que el color verd es representi amb l'string "00ff00".

En aquest cas has d'escriure un objecte que implementi la interfície KSerializer per a la classe Color:

object ColorAsStringSerializer : KSerializer<Color> {

    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Color) {
        val string = value.rgb.toString(16).padStart(6, '0')
        encoder.encodeString(string)
    }

    override fun deserialize(decoder: Decoder): Color {
        val string = decoder.decodeString()
        return Color(string.toInt(16))
    }
}

El serialitzador té una propietat i dos funcions que s'han de sobreescirue:

  1. La funció serialize implementa SerializationStrategy: rep una instància d'Encoder i un valor per serialitzar. Dins la funció fas servir les funcions encodeXxx de Encoder per representar un valor com una seqüència de primitives. Hi ha un encodeXxxper a cada tipus primari que es pot serialitzar. En el nostre exemple, utilitzem encodeString.

  2. La funció deserialize implementa DeserializationStrategy . Rep una instància de Decoder i retorna a valor deserialitzat. Fa servir les funcions decodeXxx de Decoder, que reflecteixen les funcions corresponents de Encoder. En el nostre exemple utilitzem decodeString.

  3. La propietat descriptor ha d'explicar exactament que fan les funcions encodeXxx i decodeXxx de tal manera que una implementació d'un format sàpiga per endavant quins mètodes de codificació/descodificació criden. Alguns formats també poden utilitzar-lo per generar un esquema per a les dades serialitzades. Per a la serialització de tipus primaris, s'ha d'utilitzar la funció PrimitiveSerialDescriptor amb un nom únic de tipus que s'està serialitzant. PrimitiveKind descriu el mètode específic encodeXxx / decodeXxx que s'utilitza en la implementació.

Quan la propietat descriptor no es correspon amb els mètodes de codificació/descodificació, llavors el comportament del codi resultant és incert, i pot canviar arbitràriament en futures actualitzacions.

Quan anotes una classe amb @Serializable el serialitzador per defecte utilitza el serialitzador creat de manera automàtica pel plugin del compilador.

Però aquest no és el cas.

El que has de fer és vincular un serialitzador a una classe amb l'anotació @Serializable i la propietat with:

@Serializable(with = ColorAsStringSerializer::class)
class Color(val rgb: Int)

Si ara serialitzem la classe Color com hem fet abans:

fun main() {
    val green = Color(0x00ff00)
    println(Json.encodeToString(green))
}  

El resultat és que el color es representa amb un string hexadecimal tal com voliem:

"00ff00"

Com que també has implementat el mètode deserialize pots deserialitzar colors codificats amb un string:

@Serializable(with = ColorAsStringSerializer::class)
class Color(val rgb: Int)

fun main() {
    val color = Json.decodeFromString<Color>("\"00ff00\"")
    println(color.rgb) // prints 65280 
}

També funciona si serialitzem o deserialitzem una altre classe amb alguna propietat que té el tipus Color:

@Serializable(with = ColorAsStringSerializer::class)
data class Color(val rgb: Int)

@Serializable 
data class Settings(val background: Color, val foreground: Color)

fun main() {
    val data = Settings(Color(0xffffff), Color(0))
    val string = Json.encodeToString(data)
    println(string)
    require(Json.decodeFromString<Settings>(string) == data)
}

Pots veure que les dos propietats de tipus Colores serialitzen com strings:

{ "background": "ffffff", "foreground": "000000" }

Delegació

Imagina que vols serialitzar la classe Color amb un tipus que no sigui primitiu, per exemple un IntArray.

El que fem és implementar KSerializer realitzant una conversió de Color a IntArray per tal de sigui el serialitzador de IntArray el que s'encarregui de realitzar la serialització mitjançant les funcions encodeSerializableValue i decodeSerializableValue.

import kotlinx.serialization.builtins.IntArraySerializer

class ColorIntArraySerializer : KSerializer<Color> {
    
     private val delegateSerializer = IntArraySerializer()
   
     override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: Color) {
        val data = intArrayOf(
            (value.rgb shr 16) and 0xFF,
            (value.rgb shr 8) and 0xFF,
            value.rgb and 0xFF
        )
        encoder.encodeSerializableValue(delegateSerializer, data)
    }

    override fun deserialize(decoder: Decoder): Color {
        val array = decoder.decodeSerializableValue(delegateSerializer)
        return Color((array[0] shl 16) or (array[1] shl 8) or array[2])
    }
}

Tingues en compte que no podem utilitzar el valor predeterminat Color.serializer().descriptor perquè alguns formats que depenen de l'esquema podríen pensar que podrien executar la funció encodeInt en lloc de encodeSerializableValue.

Tampoc podem utilitzar directament IntArraySerializer().descriptor directament perquè alguns formats que gestionen de manera particular els arrys d'enters no podrien saber si value és un IntArray o un Color.

En aquest enllaç tens un exemple de com un format pot tractar array de manera específica: Formats guide.

Ja pots utilitzar el nou serialitzador:

@Serializable(with = ColorIntArraySerializer::class)
class Color(val rgb: Int)

fun main() {
    val green = Color(0x00ff00)
    println(Json.encodeToString(green))
} 

Com pots veure, aquesta representació d'array no és molt útil en JSON, però pot estalviar espai quan s'utilitza amb un ByteArray i un format binari.

[0,255,0]

Surrogate

A vegades si volem codificar un objecte amb una estructura diferent, la forma més ràpida es:

  1. Crear una classe amb l'estructura desitjada anotada amb @Serializable perqué es geniri de manera automàtica el serialitzador corresponent.

  2. Convertir l'objecte a l'objecte amb la nova estructura, i serialitzar aquest objecte.

És un procediment molt semblant al que has vist abans.

A continuació serialitzarem la classe Color te tal manera que la representació JSON sigui la d'una objecte amb tres propietats r, g, i b;

  1. Definim una classe substitutiva que tingui la forma serialitzada de Color que farem servir per a la seva serialització.

  2. Definim el SerialName d'aquesta classe substituta com a Color de tal manera que si un format utilitza aquest nom (ho veurem en l'activitat TODO(link polimorfisme) escriurà "Color" i no pas "SerialColor".

  3. La classe substituta la fem private i aplica totes les restriccions a la representació serialitzada de la classe en el seu bloc init.

@Serializable
@SerialName("Color")
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
    init {     
        require(r in 0..255 && g in 0..255 && b in 0..255)
    }
}

TODO revisar

Ara podem utilitzar la funció ColorSurrogate.serializer() per recuperar un serialitzador generat per plugin per a la classe substituta.

Podem utilitzar el mateix procediment quen en "delegating serializer", però aquesta cop estem reutilitzant completament un SerialDescriptor generat de manera automàtica per a la classe substituta perquè no s'ha de distingir de l'original.

object ColorSerializer : KSerializer<Color> {
    
    override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor

    override fun serialize(encoder: Encoder, value: Color) {
        val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
        encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
    }

    override fun deserialize(decoder: Decoder): Color {
        val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
        return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
    }
}

Viculem el serialitzador ColorSerializer a la classe Color:

@Serializable(with = ColorSerializer::class)
class Color(val rgb: Int)

Ara el resultat és un objecte JSON tal com voliem:

{ "r": 0, "g": 255, "b": 0}

Hand-written composite serializer

Hi ha casos en què una solució substitutiva no funciona, perquè potser:

  • Vols evitar la degradació en el rendiment que té la creació d'un nou objecte.

  • Vols un conjunt de propietats dinàmiques o configurables per a representar el resultat del procés de serialització.

En aquests casos hem d'escriure manualment una classe serialitzador que imita el comportament d'un serialitzador generat de manera automàtica.

object ColorAsObjectSerializer : KSerializer<Color> {

En primer lloc, defineix un descriptor mitjançant el constructor buildClassSerialDescriptor. La funció element del constructor DSL recupera automàticament els serialitzadors per als camps corresponents pel seu tipus. L'ordre dels elements és important perqué s'indexen a partir de zero:

    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("Color") {
            element<Int>("r")
            element<Int>("g")
            element<Int>("b")
        }

Utilitzem "element" de manera genèric perquè que què és un element d'un descriptor depèn del seu SerialKind: els elements d'un descriptor de classe són les seves propietats, els elements d'un descriptor d'enum són els seus casos, etc.

Després escrivim la funció serialize mitjançant el DSL encodeStructure que proporciona accés al CompositeEncoder al seu bloc. La diferència entre Encoder i CompositeEncoder és que CompositeEncoder té funcions encodeXxxElement que corresponen a les funcions encodeXxx de Encoder.

S'han de cridar en el mateix ordre que en el descriptor:

    override fun serialize(encoder: Encoder, value: Color) =
        encoder.encodeStructure(descriptor) {
            encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)
            encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)
            encodeIntElement(descriptor, 2, value.rgb and 0xff)
        }

El troç de codi més complexe és la funció deserialize.

Ha de tenir en compte que hi ha formats com JSON que poden decodificar propietats en un ordre arbitrari.

Per exemple, no hi ha cap garantia que al decodificar l'string { "r": 0, "g": 255, "b": 0 } els valors es processin en l'ordre r, g, b.

La implementació comença amb una crida a decodeStructure per tenir accés al CompositeDecoder

Al seu interior escrivim un bucle que crida repetidament decodeElementIndex per descodificar l'índex del següent element, després descodifiquem l'element corresponent utilitzant decodeIntElement en el nostre exemple i, finalment, finalitzem el bucle quan trobem el valor CompositeDecoder.DECODE_DONE:


    override fun deserialize(decoder: Decoder): Color =
        decoder.decodeStructure(descriptor) {
            var r = -1
            var g = -1
            var b = -1
            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> r = decodeIntElement(descriptor, 0)
                    1 -> g = decodeIntElement(descriptor, 1)
                    2 -> b = decodeIntElement(descriptor, 2)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            require(r in 0..255 && g in 0..255 && b in 0..255)
            Color((r shl 16) or (g shl 8) or b)
        }

A continuació podem vincular aquest serialitzador a la classe Colori provar la seva serialització/deserialització:

@Serializable(with = ColorAsObjectSerializer::class)
data class Color(val rgb: Int)

fun main() {
    val color = Color(0x00ff00)
    val string = Json.encodeToString(color) 
    println(string)
    require(Json.decodeFromString<Color>(string) == color)
}  

Com abans, tenim el classe Color classe representada com un objecte JSON amb tres claus:

{ "r": 0, "g": 255, "b": 0 }

Serialització de classes de tercers

De vegades, una aplicació ha de funcionar amb un tipus extern que no és serializable. Utilitzem java.util.Date com a exemple.

Com abans, comencem escrivint una implementació de KSerializer per a la classe. El nostre objectiu és aconseguir un Date serialitzat com un nombre llarg en mil·lisegons.

object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

De moment és fàcil, excepte que no podem editar la classe java.util.Date per vincular el serialitzador DateAsLongSerializer.

Hi ha diverses maneres de solucionar-ho.

Passar un serialitzador manualment

Totes les funcions encodeToXxx i decodeFromXxx tenen una sobrecàrrega amb el primer paràmetre del serialitzador.

Quan una classe no serialitzable, com Date, és la classe de nivell superior que s'està serialitzant, les podem utilitzar:

fun main() {                                              
    val date = SimpleDateFormat("yyyy-MM-ddX").parse("2024-07-14+00") 
    println(Json.encodeToString(DateAsLongSerializer, date))    
}

El resultat és un número (TODO tornar a computar):

1455494400000

Especificació del serialitzador en una propietat

Quan una propietat d'una classe no serializable, com Date, es serialitza com a part d'una classe serialitzable, hem d'anotar la propietat amb l'anotació @Serializable o el codi no compilarà:

@Serializable          
class Person(
    val name: String,
    @Serializable(with = DateAsLongSerializer::class)
    val birthDate: Date
)

fun main() {
    val data = Person("Henry", SimpleDateFormat("yyyy-MM-ddX").parse("1973-02-15+00"))
    println(Json.encodeToString(data))
}

La propietat birthDate es serialitza amb l'estratègia de serialització que hem especificat per a ella:

{ "name": "Henry", "birthDate": 1455494400000 }

Especificació del serialitzador per a un tipus concret

L'anotació @Serializable també es pot aplicar directament als tipus. Això és útil quan una classe que requereix un serialitzador personalitzat, com ara Date, passa a ser un argument de tipus genèric.

El cas d'ús més comú és quan tens una llista de dates:

@Serializable          
class ProgrammingLanguage(
    val name: String,
    val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date>
)

fun main() {
    val df = SimpleDateFormat("yyyy-MM-ddX")
    val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00")))
    println(Json.encodeToString(data))
}
{ "name": "Kotlin", "releaseDates": [1688601600000,1682380800000,1672185600000] }

Especificació de serialitzadors per a un fitxer

Un serialitzador per a un tipus específic, com ara Date, es pot especificar per a tot un fitxer de codi font amb l'anotació UseSerializers al principi del fitxer:

@file:UseSerializers(DateAsLongSerializer::class)

Ara una propietat de tipus Date es pot utilitzar en una classe serialitzable sense anotacions addicionals:

@Serializable          
class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)

fun main() {
    val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
    println(Json.encodeToString(data))
}
{"name":"Kotlin","stableReleaseDate":1455494400000}

Especificació del serialitzador globalment mitjançant àlies de tipus

kotlinx.serialization acostuma a ser el framework sempre explícit quan es tracta d'estratègies de serialització: normalment, s'han d'esmentar explícitament a l'anotació @Serializable. Per tant, no es necessita un configuració global del serialitzador que s'ha de d'utilitzar.

No obstant, en projectes amb un gran nombre de fitxers i classes, pot ser massa complicat tenir que especificar @file:UseSerializers en cada fitxer cada cop que és necessari, sobretot per a classes com Date o Instant que tenen una estratègia fixa de serialització en tot el projecte.

En aquests casos, és possible especificar serialitzadors utilitzant typealias, ja que conserven les anotacions, incloses les relacionades amb la serialització:


typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date
typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date

Utilitzant aquests nous tipus diferents, és possible serialitzar un Date de manera diferent sense anotacions addicionals:

@Serializable          
class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong)

fun main() {
    val format = SimpleDateFormat("yyyy-MM-ddX")
    val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00"))
    println(Json.encodeToString(data))
}
{"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000}

Serialitzadors personalitzats per a un tipus genèric

Fem una ullada a l'exemple següent de la classe genèrica Box<T>.

Està marcada amb l'anotació @Serializable(with = BoxSerializer::class) ja que tenim previst tenir una serialització personalitzada.

@Serializable(with = BoxSerializer::class)
data class Box<T>(val contents: T) 

Una implementació de KSerializer per a un tipus normal s'escriu com a object, com hem vist abans amb el tipus Color.

Un serialitzador de classe genèric s'instancia amb serialitzadors pels seus paràmetres genèrics tal com hem vist abans a "Plugin-generated generic serializer".

Un serialitzador personalitzat per a una classe genèrica ha de ser una class amb un constructor que accepti tants paràmetres KSerializer paràmetres com el número de paràmetres genèrics que té el tipus.

Escrivim un serialitzador Box<T> que serialitzador que s'esborra durant la serialització, delegant-ho tot al serialitzador subjacent de l seva propietat data.

class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
   
    override val descriptor: SerialDescriptor = dataSerializer.descriptor
    override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)

    override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
}

Ara podem serialitzar i deserialitzar Box<Project>:

@Serializable
data class Project(val name: String)

fun main() {
    val box = Box(Project("kotlinx.serialization"))
    val string = Json.encodeToString(box)
    println(string)
    println(Json.decodeFromString<Box<Project>>(string))
}

Podeu obtenir el codi complet aquí .

El JSON resultant s'assembla a la classe Project que es va serialitzar directament:

{"name":"kotlinx.serialization"}
Box(contents=Project(name=kotlinx.serialization))

Serialitzadors específics del format

Els serialitzadors personalitzats anteriors funcionaven de la mateixa manera per a tots els formats.

Tanmateix, pot haver-hi característiques específiques del format que una implementació de serialitzador voldria aprofitar:

  • La secció Json transformations ofereix exemples de serialitzadors que utilitzen funcions específiques de JSON.

  • Una implementació de format pot tenir una representació específica del format per a un tipus, tal com s'explica a Format-specific type.

Aquest activitat continua amb un enfocament genèric per ajustar l'estratègia de serialització en funció del context.

Serialització contextual

Fins ara has utilitzat estratèiges estàtiques de serialització, és a dir, definides en temps de compilació, excepte "Passing a serializer manually".

Però a vegades, quan has de serialitzar una estructura complexa d'objectes, potser t'interessa canviar en temps d'execució l'estrategia de serialització en funció del context.

Per exemple, en funció del protocol pel qual s'estan serialitzant les dades t'interessa representar java.util.Date en format JSON com un string o com un enter.

Això s'anomena serialització contextual i has d'utilitzar la classe ContextualSerializer.

Normalment no utilitzes aquesta classe de manera explícita, sinó que pots utilitzar l'anotació Contextual enlloc de l'anotació @Serializable(with = ContextualSerializer::class) , o l'anotació UseContextualSerialization a nivell de fitxer.

Aquí tens un exemple:

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*

import java.util.Date
import java.text.SimpleDateFormat

@Serializable          
class ProgrammingLanguage(
    val name: String,
    @Contextual 
    val stableReleaseDate: Date
)

fun main() {
    val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
    println(Json.encodeToString(data))
}

Per serialitzar aquesta classe has de proporcionar el context corresponent quan cridem les funcions encodeToXxx / decodeFromXxx.

Com que no ho has fet es produeix l'execpció "Serializer for class 'Date' is not found".

Serializers module

Per proporcionar un context, definim una instància de SerializersModule que descriu quins serialitzadors s'utilitzaran en temps d'execució per serialitzar les classes serialitzables contextualment.

Això es fa utilitzant la funció constructora SerializersModule{} que proporciona el DSL SerializersModuleBuilder per registrar serialitzadors.

La classe corresponent per a la qual es defineix aquest serialitzador s'obté automàticament mitjançant el paràmetre de tipus reified:

private val module = SerializersModule { 
    contextual(DateAsLongSerializer)
}

A continuació, creem una instància del format Json amb aquest mòdul utilitzant la funció constructora Json{} i la propietat serializersModule:

val format = Json { serializersModule = module }

Ara ja podem serialitzar les nostres dades amb aquest format:

fun main() { val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) println(format.encodeToString(data)) }


```json
{"name":"Kotlin","stableReleaseDate":1455494400000}

Serialització contextual i classes genèriques

A la secció anterior vam veure que podem registrar una instància del serialitzador al mòdul per a una classe que volem serialitzar contextualment.

També sabem que els serialitzadors per a classes genèriques tenen paràmetres de constructor: serialitzadors d'arguments de tipus.

Això vol dir que no podem utilitzar una instància de serialitzador per a una classe si aquesta classe és genèrica:

val incorrectModule = SerializersModule {
    // Can serialize only Box<Int>, but not Box<String> or others
    contextual(BoxSerializer(Int.serializer()))
}

Per als casos en què es vol serialitzar contextualment una classe genèrica, és possible registrar el proveïdor al mòdul:

val correctModule = SerializersModule {
    // args[0] contains Int.serializer() or String.serializer(), depending on the usage
    contextual(Box::class) { args -> BoxSerializer(args[0]) } 
}

TODO Es donen detalls addicionals sobre els mòduls de serialització a la Fusió de mòduls de serialitzadors de biblioteques secció de el capítol de polimorfisme.