kotlinx.serialization — une bibliothèque multiplateforme de JetBrains pour convertir les objets Kotlin en JSON, ProtoBuf, CBOR et d’autres formats sans utiliser la réflexion. Contrairement à Gson et Moshi, elle génère le code du sérialiseur à la compilation via l’annotation @Serializable, offrant des performances élevées et la sécurité des types. Selon GitHub Kotlin/kotlinx.serialization, la bibliothèque prend en charge Kotlin/JVM, Kotlin/Native, Kotlin/JS et Kotlin/Wasm.
Points clés
kotlinx.serialization est une bibliothèque de sérialisation intégrée pour Kotlin, développée par JetBrains dans le cadre de l’écosystème officiel Kotlin. Sa principale différence par rapport aux solutions tierces (Gson, Moshi, Jackson) est qu’elle n’utilise pas la réflexion à l’exécution. Au lieu de cela, le code du sérialiseur est généré à la compilation avec Kotlin Symbol Processing (KSP) ou le plugin du compilateur Kotlin. Cela offre un gain de performances de 3 à 5 fois par rapport à Gson et garantit la sécurité des types.
La bibliothèque prend officiellement en charge quatre formats : JSON (via le module kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) et HOCON (kotlinx-serialization-hocon). Les formats sont ajoutés comme des dépendances séparées dans build.gradle.kts, évitant d’inclure des bibliothèques inutiles dans le projet. Chaque format possède son propre ensemble de paramètres de configuration.
Multiplateforme est une fonctionnalité clé de la bibliothèque. La même classe avec @Serializable fonctionne sur toutes les cibles : JVM (Android, Backend), Native (iOS), JS (Web, React) et Wasm (WebAssembly). Le développeur n’a pas besoin d’écrire différentes implémentations de sérialisation pour chaque plateforme — le code reste le même. C’est particulièrement précieux dans les projets Kotlin Multiplatform Mobile (KMM) où le code partagé est utilisé sur Android et iOS.
La génération de code dans kotlinx.serialization se déroule en trois étapes. À la première étape, le compilateur Kotlin détecte l’annotation @Serializable sur une classe et la transmet au plugin Kotlin Symbol Processing (KSP). À la deuxième étape, KSP génère un objet sérialiseur implémentant l’interface KSerializer. À la troisième étape, le code généré est compilé avec le code source du projet. En résultat, aucune de ces étapes n’est exécutée pendant l’exécution de l’application.
Le sérialiseur généré travaille directement avec les champs de la classe via leurs getters et setters, sans réflexion. Cela signifie que les champs avec le modificateur private sont également sérialisés s’ils sont marqués avec @Serializable. Les performances de cette approche sont proches de la sérialisation manuelle : pour les classes simples (5–10 champs), le temps de sérialisation est de 10–50 microsecondes ; pour les graphes d’objets complexes, jusqu’à 200 microsecondes pour 1000 objets.
Pour ajouter la bibliothèque à un projet Android ou Kotlin/JVM, vous devez ajouter le plugin et les dépendances dans build.gradle.kts. Le plugin org.jetbrains.kotlin.plugin.serialization, dont la version correspond à celle de Kotlin, active la génération de code. La bibliothèque kotlinx-serialization-json est ajoutée dans la section dependencies avec une version indépendante de la version de Kotlin.
// build.gradle.kts — ajout de kotlinx.serialization
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// Module principal de sérialisation
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// Formats supplémentaires
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON est le format le plus populaire dans kotlinx.serialization. Pour sérialiser un objet, il suffit d’annoter la data class avec @Serializable et d’appeler Json.encodeToString(). Pour la désérialisation, appelez Json.decodeFromString() en spécifiant le type. La bibliothèque gère automatiquement les champs null, les listes, les objets imbriqués et les énumérations. Tous les champs de la classe sont obligatoires par défaut, sauf indication contraire.
La configuration JSON se fait via le Json {} builder. Vous pouvez passer ignoreUnknownKeys = true pour ignorer les champs inconnus lors de la désérialisation, prettyPrint = true pour une sortie formatée, coerceInputValues = true pour convertir les valeurs invalides en valeurs par défaut. Sont également disponibles encodeDefaults (sérialiser les champs avec leurs valeurs par défaut) et classDiscriminator (nom du champ pour la sérialisation polymorphe).
// Exemple de sérialisation et désérialisation JSON
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
@Serializable
data class Project(
val name: String,
val stars: Int,
val isActive: Boolean = true,
val languages: List<String> = emptyList()
)
fun main() {
val project = Project(
name = "kotlinx.serialization",
stars = 7200,
languages = listOf("Kotlin", "Java")
)
// Sérialisation JSON avec prettyPrint
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// Désérialisation depuis JSON
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
L’exemple illustre le cycle de base de la sérialisation et de la désérialisation. Une data class Project avec l’annotation @Serializable obtient automatiquement encodeToString et decodeFromString. Le champ isActive a une valeur par défaut true — si ce champ est absent du JSON, la valeur par défaut est utilisée. Si des champs inconnus arrivent dans JSON sans ignoreUnknownKeys = true, une SerializationException est levée.
Les sealed class sont l’un des cas d’utilisation les plus puissants de kotlinx.serialization. La bibliothèque prend en charge la sérialisation polymorphe pour les hiérarchies de sealed class sans configuration supplémentaire : il suffit d’annoter la sealed class et toutes ses sous-classes avec @Serializable. Lors de la sérialisation, un champ « type » est ajouté (configurable via classDiscriminator), qui détermine le type spécifique lors de la désérialisation.
// Sérialisation polymorphe de sealed class
@Serializable
sealed class Response
@Serializable
data class Success(val data: String) : Response()
@Serializable
data class Error(val code: Int, val message: String) : Response()
fun main() {
val json = Json { classDiscriminator = "result_type" }
val responses: List<Response> = listOf(
Success(data = "Data loaded"),
Error(code = 404, message = "Not found")
)
val jsonString = json.encodeToString(responses)
println(jsonString)
/*
[
{"result_type":"Success","data":"Data loaded"},
{"result_type":"Error","code":404,"message":"Not found"}
]
*/
val decoded = json.decodeFromString<List<Response>>(jsonString)
when (val first = decoded[0]) {
is Success -> println("Success: ${first.data}")
is Error -> println("Error: ${first.code}")
}
}
La sérialisation polymorphe des sealed class est particulièrement utile dans les clients API où le serveur renvoie différents types de réponses. Sans kotlinx.serialization, il faudrait écrire un désérialiseur manuel avec un when sur le champ discriminant. Avec la bibliothèque, cela se fait avec une seule annotation. classDiscriminator permet de renommer le champ marqueur (par défaut « type ») en n’importe quelle valeur attendue par le serveur.
La bibliothèque fournit un ensemble d’annotations pour affiner la sérialisation. La principale est @Serializable pour une classe. Les supplémentaires : @SerialName pour définir le nom d’un champ dans JSON (s’il diffère du nom Kotlin), @Transient pour exclure un champ de la sérialisation, @Required pour un champ qui doit être présent dans JSON, @EncodeDefault pour forcer la sérialisation d’un champ même avec sa valeur par défaut.
| Annotation | Objectif | Exemple |
|---|---|---|
| @Serializable | Active la génération du sérialiseur pour une classe | @Serializable data class User |
| @SerialName | Définit un nom alternatif pour le champ dans le format | @SerialName(«user_name») val name: String |
| @Transient | Exclut un champ de la sérialisation | @Transient val cache: MutableMap |
| @Required | Champ obligatoire dans JSON lors de la désérialisation | @Required val id: String |
| @EncodeDefault | Sérialise le champ même avec sa valeur par défaut | @EncodeDefault val type: Type = Type.A |
| @Serializer | Lie un sérialiseur personnalisé à une classe | @Serializer(forClass = Date::class) |
L’annotation @SerialName est cruciale lors du travail avec des API où les noms de champs sont en snake_case, tandis que le style Kotlin est le camelCase. Par exemple, le serveur envoie « user_id », mais le code Kotlin utilise userId. @SerialName(« user_id ») résout ce problème sans mappeurs supplémentaires. @Transient est utile pour les champs qui ne doivent pas être envoyés au serveur — comme les valeurs calculées temporaires ou les caches.
Par défaut, tous les champs dans kotlinx.serialization sont obligatoires. Si un champ peut être absent dans JSON, vous devez le rendre nullable (String?) ou définir une valeur par défaut (val name: String = « »). Cependant, il existe des situations où un champ est non nullable en Kotlin mais peut être absent dans JSON en raison du versionnage de l’API. Dans ce cas, @Required lève une SerializationException lorsque le champ est absent, tandis qu’une valeur par défaut le remplit sans erreur.
KSerializer est l’interface que tous les sérialiseurs dans kotlinx.serialization implémentent. Si la génération de code standard ne convient pas (par exemple, pour travailler avec Date, Bitmap ou un format binaire spécifique), vous pouvez écrire votre propre sérialiseur. Pour ce faire, implémentez les méthodes serialize() et deserialize(), et fournissez un descripteur — une description de la structure pour le schéma du format.
Les sérialiseurs personnalisés sont connectés de deux manières : via l’annotation @Serializable(with = MySerializer::class) pour lier à une classe spécifique, ou globalement via Json { serializersModule = ... } pour lier à toutes les instances d’un type. La deuxième méthode est préférable pour les types intégrés (Date, UUID) afin d’éviter d’écrire une annotation sur chaque champ.
// Sérialiseur personnalisé pour java.util.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object DateSerializer : KSerializer<Date> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(dateFormat.format(value))
}
override fun deserialize(decoder: Decoder): Date {
return dateFormat.parse(decoder.decodeString())
}
}
// Utilisation d’un sérialiseur personnalisé
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("Release", Date())
println(json.encodeToString(event))
}
Dans l’exemple, DateSerializer convertit java.util.Date en une chaîne ISO 8601. Sans un sérialiseur personnalisé, kotlinx.serialization ne peut pas travailler avec Date — c’est un type qui ne fait pas partie de la bibliothèque standard de Kotlin. @Serializable(with = DateSerializer::class) sur un champ spécifique lie le sérialiseur uniquement pour ce champ. Pour l’enregistrement global de toutes les Dates, utilisez Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.
kotlinx.serialization ne se limite pas au JSON. La bibliothèque prend en charge quatre formats intégrés, chacun avec son propre module et sa configuration. JSON (kotlinx-serialization-json) est universel, lisible par l’homme, adapté aux API REST. ProtoBuf (kotlinx-serialization-protobuf) est binaire, compact, avec un schéma obligatoire, pour les microservices à forte charge. CBOR (kotlinx-serialization-cbor) est un équivalent binaire de JSON, pratique pour l’IoT et les appareils mobiles à bande passante limitée. HOCON (kotlinx-serialization-hocon) est un format de configuration compatible avec TypeSafe Config.
| Format | Module | Type | Schéma | Utilisation typique |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Texte | Optionnel | API REST, stockage de données |
| ProtoBuf | kotlinx-serialization-protobuf | Binaire | Obligatoire (.proto) | Microservices, gRPC |
| CBOR | kotlinx-serialization-cbor | Binaire | Optionnel | IoT, appareils mobiles |
| HOCON | kotlinx-serialization-hocon | Texte | Optionnel | Fichiers de configuration |
ProtoBuf nécessite la définition d’un schéma dans des fichiers .proto, mais kotlinx-serialization-protobuf génère des classes Kotlin directement à partir de @Serializable sans .proto. Cela simplifie le développement : il suffit d’annoter la data class et d’utiliser ProtoBuf.encodeToByteArray(). CBOR est particulièrement pertinent pour Android lorsqu’il est nécessaire de transférer des données binaires compactes via NFC ou BLE. Les messages CBOR sont en moyenne 20–30% plus petits que JSON pour le même ensemble de données.
Pour les API REST dans une application mobile, JSON est le meilleur choix — il peut être débogué sans outils supplémentaires, est lisible dans les logs et compatible avec tout backend. Si l’application transfère de grands volumes de données entre microservices (centaines de mégaoctets), ProtoBuf offre un avantage de vitesse jusqu’à 5x grâce au codage binaire. Pour stocker les paramètres dans des fichiers, utilisez HOCON ou JSON. Pour les appareils avec des limites de trafic strictes (capteurs IoT), utilisez CBOR.
La première erreur est d’ignorer les clés inconnues lors de la désérialisation. Si le serveur ajoute un nouveau champ et que vous avez ignoreUnknownKeys = false, l’application plantera avec une SerializationException. Ce drapeau est désactivé par défaut. Solution : définissez toujours Json { ignoreUnknownKeys = true } pour le code de production afin d’être résistant aux changements d’API.
La deuxième erreur est de sérialiser les champs internal ou private dans une data class. Dans une data class Kotlin, tous les champs du constructeur primaire sont sérialisés par défaut. Si un champ contient des données sensibles (mot de passe, jeton), il doit être marqué avec @Transient ou retiré du constructeur primaire. @Transient exclut complètement le champ du JSON, mais à l’intérieur du constructeur, il peut causer une erreur — il est préférable de définir ces champs dans le corps de la classe avec @Transient.
La troisième erreur est la sérialisation polymorphe sans sealed class. Si vous utilisez une open class au lieu de sealed, kotlinx.serialization nécessite l’enregistrement explicite de toutes les sous-classes dans serializersModule. Contrairement aux sealed class, où le compilateur connaît toutes les sous-classes, les open class permettent une extension arbitraire — la bibliothèque ne peut pas déterminer automatiquement tous les sous-types. L’enregistrement se fait via Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.
La version de kotlinx.serialization doit être compatible avec la version de Kotlin. JetBrains publie un tableau de compatibilité : kotlinx-serialization 1.6.x est compatible avec Kotlin 1.9.x, 1.7.x avec Kotlin 2.0.x et 2.1.x. Une inadéquation de version provoque des erreurs de compilation cryptiques comme « Symbol ‘serializer’ is missing ». Vérifiez toujours la version actuelle sur Maven Central ou dans le dépôt GitHub du projet.
Foire aux questions
kotlinx.serialization utilise la génération de code à la compilation via KSP, tandis que Gson et Moshi utilisent la réflexion à l’exécution. Cela offre un avantage de performances (3–5x plus rapide que Gson) et la sécurité des types. Gson sérialise tout champ sans annotation, ce qui peut entraîner des fuites de données. kotlinx.serialization nécessite l’annotation explicite @Serializable, ce qui est plus sûr. Moshi prend également en charge codegen, mais uniquement pour JVM et Android.
Oui, kotlinx.serialization est une bibliothèque multiplateforme officielle de JetBrains. Elle fonctionne sur Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) et Kotlin/Wasm. L’API est unifiée sur toutes les plateformes : @Serializable + Json.encodeToString() fonctionne partout de la même manière. Pour iOS, aucune configuration supplémentaire n’est nécessaire — Kotlin/Native compile le code sérialisé en un binaire natif.
Les champs nullable (String?) sont désérialisés comme null si la valeur est absente ou nulle dans JSON. Pour les champs non nullable (String) sans valeur par défaut, l’absence du champ dans JSON lèvera une SerializationException. Si vous souhaitez que les valeurs null n’apparaissent pas dans JSON, configurez Json { encodeDefaults = false }. Cela exclut tous les champs égaux à leur valeur par défaut (y compris null pour les types nullable).
Utilisez @SerialName(«nom_en_snake_case») sur chaque champ dont le nom diffère du format Kotlin. Alternativement, pour Kotlin 2.0+, Json { namingStrategy = JsonNamingStrategy.SnakeCase } est disponible pour la conversion automatique camelCase ↔ snake_case. Ce paramètre s’applique à tous les champs à la fois. Si une personnalisation partielle est nécessaire, combinez @SerialName avec la stratégie globale.
Non, Flow et les coroutines ne sont pas directement sérialisables — ils représentent une exécution asynchrone, pas des données. Pour transférer des données d’un Flow, collectez-les dans une collection via .toList() dans une coroutine et sérialisez la collection. De même, vous ne pouvez pas sérialiser Job, Deferred ou Continuation. Sérialisez uniquement les data classes — des modèles de données sans logique comportementale.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi