- Sérialisation et désérialisation de flux d'entrée et sortie Java. Vous pouvez désormais lire et écrire du JSON directement dans des flux ou des fichiers réseau.
- Plus de contrôle sur l'encodage des valeurs par défaut. Une nouvelle annotation définit l'encodage des valeurs par défaut pour chaque propriété séparément.
- L'exclusion des valeurs null vous permet de minimiser le JSON résultant en supprimant les valeurs de propriété null et en les restaurant lors de la désérialisation.
- Les discriminateurs de classes polymorphes personnalisés augmentent la lisibilité et ajoutent une sémantique à JSON lorsque vous travaillez avec des hiérarchies de classes.
Sérialisation JSON basée sur le flux d'entrée et sortie Java (Java IO)
La sérialisation vers les flux Java IO et leur désérialisation sont demandées par les utilisateurs depuis un certain temps. Dans kotlinx.serialization 1.3.0, JetBrains présente enfin la première version expérimentale de l'API de sérialisation pour les flux d'entrée et sortie. Avec cette API, vous pouvez décoder des objets directement à partir de fichiers, de flux réseau et d'autres sources de données sans lire au préalable les données dans les chaînes. L'opération inverse est également disponible : vous pouvez envoyer des objets encodés directement vers des fichiers et d'autres flux en un seul appel API.
La sérialisation des flux IO est actuellement disponible uniquement sur la plateforme JVM et pour le format JSON. L'API comprend deux méthodes principales :
- Json.decodeFromStream(), qui lit un flux d'entrée et en désérialise un objet du type donné.
- Json.encodeToStream(), qui sérialise un objet et l'envoie dans le flux de sortie donné.
Voici comment vous pouvez lire un objet JSON à partir d'une URL et l'écrire dans un fichier :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Serializable data class Project( val name: String, val language: String, ) @OptIn(ExperimentalSerializationApi::class) fun main() { URL("https://example.com/project.json").openStream().use { val project = Json.decodeFromStream<Project>(it) // read JSON from a URL println(project) FileOutputStream(File("project.json")).use { // and save to a file Json.encodeToStream(project, it) } } } |
Notez que seuls les flux UTF-8 sont actuellement pris en charge.
Ce n'est que la première étape, et il reste encore beaucoup de travail à faire. JetBrains vous invite donc à essayer l'API de sérialisation de flux IO dans vos projets et partager vos commentaires sur GitHub.
Plus de contrôle pour l'encodage des valeurs par défaut
kotlinx.serialization réduit la taille du JSON résultant de la sérialisation des objets en omettant les valeurs par défaut des propriétés de l'objet. Des valeurs par défaut sont définies dans la déclaration de classe et attribuées automatiquement si la propriété correspondante n'est pas initialisée dans le code :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | data class Project( val name: String, val language: String, val version: String? = "1.3.0", //default value ) fun main() { val data = Project("kotlinx.serialization", "Kotlin") // version is “1.3.0” by default } |
Les chaînes JSON produites par une configuration Json par défaut ne contiendront pas de propriétés d'objet avec des valeurs par défaut. Lors du décodage de telles chaînes JSON, toutes les propriétés omises reçoivent leurs valeurs par défaut telles que définies dans la déclaration de classe.
Cependant, vous pouvez forcer la bibliothèque à coder les valeurs par défaut en définissant la propriété encodeDefaults d'une instance Json sur true :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | val format = Json { encodeDefaults = true } @Serializable data class Project( val name: String, val language: String, val version: String? = "1.3.0", ) fun main() { val data = Project("kotlinx.serialization", "Kotlin") // version is “1.3.0” by default val json = format.encodeToString(data) println(json) // {"name":"kotlinx.serialization","language":"Kotlin","version":"1.3.0"} println(format.decodeFromString<Project>(json)) //Project(name=kotlinx.serialization, language=Kotlin, version=1.3.0) } |
Cette fonctionnalité était déjà disponible, et dans la version 1.3.0, JetBrains l'étend en ajoutant une nouvelle façon d'affiner la sérialisation des valeurs par défaut : vous pouvez désormais la contrôler au niveau de la propriété à l'aide de l'annotation expérimentale @EncodeDefault. Cela a un niveau de priorité plus élevé que la propriété encodeDefaults et prend l'une des deux valeurs possibles :
- ALWAYS (valeur par défaut) encode une valeur de propriété même si elle est égale à la valeur par défaut.
- NEVER n'encode pas la valeur par défaut quelle que soit la configuration Json.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Serializable @OptIn(ExperimentalSerializationApi::class) data class Project( val name: String, val language: String, @EncodeDefault(EncodeDefault.Mode.ALWAYS) val version: String? = "1.3.0", // or just // @EncodeDefault val version: String? = "1.3.0", ) fun main() { val data = Project("kotlinx.serialization", "Kotlin") // version is “1.3.0” by default val json = Json.encodeToString(data) println(json) // {"name":"kotlinx.serialization","language":"Kotlin","version":"1.3.0"} // default version is in JSON although encodeDefaults = false } |
L'encodage des propriétés annotées n'est pas affecté par encodeDefaults et fonctionne comme décrit pour tous les formats de sérialisation, pas seulement JSON.
Exclusion des valeurs null de la sérialisation
Dans la version 1.3.0 de la bibliothèque de sérialisation de Kotlin, JetBrains introduit une autre façon de réduire la taille des chaînes JSON générées - en omettant les valeurs null.
Une nouvelle propriété de configuration JSON, explicitNulls, définit si les valeurs de propriété null doivent être incluses dans la chaîne JSON sérialisée. Elle prend la valeur true par défaut, donc tous les null sont stockés en tant que valeurs de leurs propriétés correspondantes.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Serializable data class Project( val name: String, val language: String, val version: String? = "1.3.0", val website: String? ) fun main() { val data = Project("kotlinx.serialization", "Kotlin", null, null) val json = Json.encodeToString(data) println(json) //{"name":"kotlinx.serialization","language":"Kotlin","version":"null","website":"null"} } |
Vous pouvez raccourcir le JSON résultant en excluant les propriétés null de la sérialisation. Utilisez simplement une instance Json avec explicitNulls = false :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @OptIn(ExperimentalSerializationApi::class) val format = Json { explicitNulls = false } @Serializable data class Project( val name: String, val language: String, val version: String? = "1.3.0", val website: String?, ) fun main() { val data = Project("kotlinx.serialization", "Kotlin", null, null) val json = format.encodeToString(data) println(json) // {"name":"kotlinx.serialization","language":"Kotlin"} |
La chaîne JSON résultante contient uniquement des propriétés non NULL.
Pour désérialiser les objets depuis JSON avec des valeurs NULL omises, vous avez également besoin d'une instance Json avec explicitNulls == false. Une telle configuration définit toutes les propriétés nullables omises sur null, sauf si elles ont des valeurs par défaut. Dans ce cas, la valeur par défaut est utilisée. Voici comment la chaîne json de ce snippet est décodée :
Code : | Sélectionner tout |
1 2 3 | println(format.decodeFromString<Project>(json)) //Project(name=kotlinx.serialization, language=Kotlin, version=1.3.0, website=null) |
Essayer d'utiliser une configuration Json avec explicitNulls == true (le paramètre par défaut) pour décoder une chaîne JSON avec des valeurs null omises entraînera une MissingFieldException.
Discriminateurs de classes polymorphes personnalisés
En ce qui concerne les hiérarchies de classes, la sérialisation peut devenir un peu difficile en raison de la nécessité supplémentaire de prendre en charge le polymorphisme dans les opérations de sérialisation. Il existe des méthodes recommandées pour gérer la sérialisation des hiérarchies : les rendre scellées, annoter chaque classe de la hiérarchie comme @Serializable, et ainsi de suite.
Dans la sérialisation hiérarchique, un attribut utile entre en jeu - le discriminateur de classe. Il sert de clé pour une propriété qui stocke la classe exacte de l'objet qui a été encodé. Par défaut, le discriminateur porte le nom « type » et contient un nom de classe complet de l'objet en cours de sérialisation, par exemple :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Serializable sealed class Project { abstract val name: String } @Serializable class OwnedProject(override val name: String, val owner: String) : Project() fun main() { val data: Project = OwnedProject("kotlinx.serialization", "kotlin") println(Json.encodeToString(data)) // {"type":"org.example.OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} // “type” property stores the class of the serialized object } |
Dans les versions précédentes, vous pouviez modifier le nom du discriminateur à l'aide de la propriété classDiscriminator de l'instance Json. Compte tenu de la hiérarchie ci-dessus, vous pouvez écrire :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | val format = Json { classDiscriminator = "#className" } fun main() { val data: Project = OwnedProject("kotlinx.serialization", "kotlin") println(format.encodeToString(data)) // {"#className":"org.example.OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} // Now the discriminator is “#className”. Its value is the same } |
Dans la version 1.3.0, JetBrains a ajouté un moyen de définir un nom de discriminateur personnalisé pour chaque hiérarchie de classe afin de permettre une sérialisation plus flexible. Vous pouvez le faire en marquant une classe avec la nouvelle annotation expérimentale @JsonClassDiscriminator, en utilisant le nom du discriminateur comme argument. Par exemple, vous pouvez utiliser un mot qui identifie d'une manière ou d'une autre toute la hiérarchie à laquelle appartient un objet :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @OptIn(ExperimentalSerializationApi::class) @Serializable @JsonClassDiscriminator("projectType") sealed class Project { abstract val name: String } @Serializable class OwnedProject(override val name: String, val owner: String) : Project() fun main() val data: Project = OwnedProject("kotlinx.serialization", "kotlin") println(Json.encodeToString(data)) // {"projectType":"org.example.OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} // Now the discriminator is “projectType”. } |
Un discriminateur personnalisé s'applique à la classe annotée et à ses sous-classes. Un seul discriminateur personnalisé peut être utilisé dans chaque hiérarchie de classe.
Pour commencer à utiliser kotlinx.serialization 1.3
Si vous utilisez déjà kotlinx.serialization, la mise à niveau vers la version 1.3 est très rapide. Et si vous n’avez pas encore essayé kotlinx.serialization, vous pouvez tester cette nouvelle version. Tout d’abord, mettez à jour le bloc plugins dans votre fichier build.gradle.kts :
Code : | Sélectionner tout |
1 2 3 4 5 | plugins { kotlin("jvm") version "1.5.31" // or kotlin("multiplatform") or any other Kotlin plugin kotlin("plugin.serialization") version "1.5.31" } |
Ensuite, mettez à jour votre bloc dependencies avec la bibliothèque d’exécution, y compris les formats que vous voulez utiliser dans votre application :
Code : | Sélectionner tout |
1 2 3 4 5 | dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") // . . . } |
Explorez la documentation d'API de Kotlinx.serialisation