# Writing and using your own serializers The most simple and straightforward way to create serializer is to write annotation `@Serializable` directly on your class: ```kotlin @Serializable class MyData(val s: String) ``` In this case, compiler plugin will generate for you: * `.serializer()` method on companion object (will be created if there is no such yet) to obtain serializer. If your class is a generic class, this method will have arguments `KSerializer, KSerializer`..., where `T1, T2` - your generic type parameters. * Special nested object in your class, which implements `KSerializer` * Methods `serialize`, `deserialize` and `patch` of interfaces `SerializationStrategy` and `DeserializationStrategy` * Descriptor property `descriptor` of `KSerializer` ## Table of contents * [Customizing default serializers](#customizing) - [Representing classes as a single value](#representing-classes-as-a-single-value) - [Representing classes as multiple values](#representing-classes-as-multiple-values) * [External serializers for library classes](#external-serializers-for-library-classes) + [About generic serializers](#about-generic-serializers) * [Using custom serializers](#using-custom-serializers) + [`UseSerializers` annotation](#useserializers-annotation) * [Registering and serial modules](#registering-and-context) + [`ContextualSerialization` annotation](#contextualserialization-annotation) ## Customizing If you want to customize representation of the class, in most cases, you need to write your own `serialize` and `deserialize` methods. `patch` method have default implementation of `throw UpdateNotSupportedException(descriptor.name)`. Serial descriptor property typically used in generated version of those methods; however, if you're using features like schema saving (which will be discussed later, once it's implemented), it is important to provide consistent implementation of it. You can write methods directly on companion object, annotate it with `@Serializer(forClass = ...)`, and serialization plugin will respect it as default serializer. Note that this is applicable only for companion objects, other nested objects would not be recognized automatically. (note you still have to apply `@Serializable` annotation, because we need to auto-generate `descriptor` even if we don't use it) #### Representing classes as a single value is pretty straightforward – pick corresponding descriptor, and use `encodeXXX/decodeXXX` methods of Encoder/Decoder. ```kotlin import kotlinx.serialization.* import kotlinx.serialization.internal.* @Serializable class MyData(val s: String) { @Serializer(forClass = MyData::class) companion object : KSerializer { override val descriptor: SerialDescriptor = StringDescriptor.withName("MyData") override fun serialize(output: Encoder, obj: MyData) { output.encodeString(HexConverter.printHexBinary(obj.s.toByteArray())) } override fun deserialize(input: Decoder): MyData { return MyData(String(HexConverter.parseHexBinary(input.decodeString()))) } } } ``` #### Representing classes as multiple values is a bit more complicated. In this case, you'll need to write a code which is similar to code that is generated by the plugin. Here will be presented a cut-and-paste recipe; you can read [KEEP](https://github.com/Kotlin/KEEP/blob/serialization/proposals/extensions/serialization.md) for details about core concepts. First, we need to correctly fill-in descriptor so all formats would know about mapping from field names to indices and vice versa: ```kotlin @Serializable class BinaryPayload(val req: ByteArray, val res: ByteArray) { @Serializer(forClass = BinaryPayload::class) companion object : KSerializer { override val descriptor: SerialDescriptor = object : SerialClassDescImpl("BinaryPayload") { init { addElement("req") // req will have index 0 addElement("res") // res will have index 1 } } } } ``` Now we need to serialize class' properties one-by-one. Since they are structured, i.e. have their own position and name inside of `BinaryPayload` class, we would use `CompositeEncoder` instead of `Encoder`: ```kotlin override fun serialize(output: Encoder, obj: BinaryPayload) { val compositeOutput: CompositeEncoder = output.beginStructure(descriptor) compositeOutput.encodeStringElement(descriptor, 0, HexConverter.printHexBinary(obj.req)) compositeOutput.encodeStringElement(descriptor, 1, HexConverter.printHexBinary(obj.res)) compositeOutput.endStructure(descriptor) } ``` Deserializing a class with multiple values is a complex task, mainly because you don't know the order of fields in the input stream in advance. So crucial part here is to make a `when` over an index of an incoming element: ```kotlin override fun deserialize(input: Decoder): BinaryPayload { val inp: CompositeDecoder = input.beginStructure(descriptor) lateinit var req: ByteArray // consider using flags or bit mask if you lateinit var res: ByteArray // need to read nullable non-optional properties loop@ while (true) { when (val i = inp.decodeElementIndex(descriptor)) { CompositeDecoder.READ_DONE -> break@loop 0 -> req = HexConverter.parseHexBinary(inp.decodeStringElement(descriptor, i)) 1 -> res = HexConverter.parseHexBinary(inp.decodeStringElement(descriptor, i)) else -> throw SerializationException("Unknown index $i") } } inp.endStructure(descriptor) return BinaryPayload(req, res) } ``` You can see it in action [in tests](https://github.com/kotlin/kotlinx.serialization/blob/a4c41392bb735a36788db1d789ec60afdbad3ca8/runtime/common/src/test/kotlin/kotlinx/serialization/features/BinaryPayloadExampleTest.kt). Another useful example from tests is [custom serializer which uses ability to read JSON as tree](https://github.com/Kotlin/kotlinx.serialization/blob/master/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonTreeAndMapperTest.kt#L35). *Note:* this approach is not working for generic classes, see below. ## External serializers for library classes Approach above will not work, if you can't modify source code of the class - e.g. it is a Kotlin/Java library class. If it is Kotlin class, you can just let the plugin know you want to create serializer from object: ```kotlin // Imagine that MyData is a third-party library class. // Plugin will try to automatically serialize all constructor properties // and public vars. @Serializer(forClass = MyData::class) object DataSerializer {} ``` This is called external serialization and imposes certain restrictions - class should have only primary constructor's vals/vars and class body `var` properties (you can learn more in [docs](examples.md)) As in the first example, you can customize the process by overriding `serialize` and `deserialize` methods. If it is Java class, things getting more complicated: because Java has no concept of primary constructor, plugin don't know which properties it can took. For Java classes, you always should override `serialize`/`deserialize` methods. You still can use `@Serializer(forClass = ...)` to generate empty serial descriptor. For example, let's write serializer for `java.util.Date`: ```kotlin @Serializer(forClass = Date::class) object DateSerializer: KSerializer { private val df: DateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS") override val descriptor: SerialDescriptor = StringDescriptor.withName("WithCustomDefault") override fun serialize(output: Encoder, obj: Date) { output.encodeString(df.format(obj)) } override fun deserialize(input: Decoder): Date { return df.parse(input.decodeString()) } } ``` See it in action [here](https://github.com/kotlin/kotlinx.serialization/blob/a4c41392bb735a36788db1d789ec60afdbad3ca8/runtime/jvm/src/test/kotlin/kotlinx/serialization/features/SerializeJavaClassTest.kt). ### About generic serializers If your class has generic type arguments, it shouldn't be an object. It must be a class with visible primary constructor, where its arguments are `KSerializer, KSerializer, etc..` - one for each type argument of your class. E.g. for given class `class CheckedData(val data: T, val checkSum: ByteArray)`, serializer would look like: ```kotlin @Serializer(forClass = CheckedData::class) class CheckedDataSerializer(val dataSerializer: KSerializer) : KSerializer> ``` If you're familiar with DI concepts, think of it as _constructor injection_ of serializers for generic properties. Note that we haven't applied `@Serializable` on the class, because we can't customize it via companion object since companion object can't have constructor arguments. *Current limitation*: Because primary constructor in such case is generated by the compiler itself, not the plugin, you have to override `descriptor` manually since it can't be initialized in non-synthetic constructor. See full sample [here](https://github.com/kotlin/kotlinx.serialization/blob/a4c41392bb735a36788db1d789ec60afdbad3ca8/runtime/jvm/src/test/kotlin/kotlinx/serialization/features/GenericCustomSerializerTest.kt). ## Using custom serializers Recommended way of using custom serializers is to give a clue to plugin about which serializer use for specified property, using annotation in form `@Serializable(with = SomeKSerializer::class)`: ```kotlin @Serializable data class MyWrapper( val id: Int, @Serializable(with=MyExternalSerializer::class) val data: MyData ) ``` This will affect generating of `save`/`load` methods only for this dedicated class, and allows plugin to resolve serializer at compile-time to reduce runtime overhead. It would also allow plugin to inject serializers for generic properties automatically. ### `UseSerializers` annotation If you have a lot of serializable classes, which use, say `java.util.Date`, it may be inconvenient to annotate every property with this type with `@Serializable(with=MyJavaDateSerializer::class)`. For such purpose, a file-level annotation `UseSerializers` was introduced. With it, you can write `@file:UseSerializers(MyJavaDateSerializer::class)` and all properties of type `java.util.Date` in all classes in this file would be serialized with `MyJavaDateSerializer`. See [its documentation](https://github.com/kotlin/kotlinx.serialization/blob/c6dd98c8b96a69da82ab8229665e07614296f684/runtime/common/src/main/kotlin/kotlinx/serialization/Annotations.kt#L75) for more details. ## Registering and context By default, all serializers are resolved by plugin statically when compiling serializable class. This gives us type-safety, performance and eliminates reflection usage to minimum. However, if there is no `@Serializable` annotation of class and no `@Serializable(with=...)` on property, in general, it is impossible to know at compile time which serializer to use - user can define more than one external serializer, or define them in other module, or even it's a class from library which doesn't know anything about serialization. To support such cases, a concept of `SerialModule` was introduced. Roughly speaking, it's a map where runtime part of framework is looking for serializers if they weren't resolved at compile time by plugin. Modules are intended to be reused in different formats or even different projects (e.g. Library A have some custom serializers and exports a module with them so Application B can use A's classes with serializers in B's output). If you want your external serializers to be used, you pass a module with them to the serialization format. All standard formats have constructor parameter `context: SerialModule`. ### `ContextualSerialization` annotation When some runtime ambiguity involved, it's always better to be explicit about your intentions — especially in such a security-sensitive thing like a serialization framework. Therefore, to be able to use Context at runtime, you need to explicitly use special ContextSerializer — otherwise compiler will report you an error about missing serializer. To enable contextual serialization, simply use `@Serializable(with=ContextSerializer::class)` or `@ContextualSerialization` on a property with type which does not have default serializer. To be less verbose, it is also possible to apply this annotation on file — `@file:ContextualSerialization(A::class, B::class)` instructs compiler plugin to use ContextSerializer everywhere in this file for properties of types `A` and `B`. It also can be used on type usages: `List<@ContextualSerialization MyDate>`. > In next releases, the same thing would be required for polymorphism and `PolymorphicSerializer`. Start using `@Polymorphic` right now! ### Example You can also install different modules for one class into different instances of output formats. Let's see it in example: ```kotlin // Imagine we have class Payload with 2 different serializers val simpleModule = serializersModuleOf(Payload::class, PayloadSerializer) // You can also create modules from map or using a builder function SerializersModule { ... } val binaryModule = serializersModuleOf(Payload::class, BinaryPayloadSerializer) val json1 = Json(context = simpleModule) val json2 = Json(context = binaryModule) // in json1, Payload would be serialized with PayloadSerializer, // in json2, Payload would be serialized with BinaryPayloadSerializer ``` See it in action [here](https://github.com/kotlin/kotlinx.serialization/blob/a4c41392bb735a36788db1d789ec60afdbad3ca8/runtime/jvm/src/test/kotlin/kotlinx/serialization/CustomSerializersJVMTest.kt#L96).