mer. Mai 25th, 2022

A new version of the Kotlin serialization library – 1.3.0 – is now available following the release of Kotlin 1.5.30. It mainly focuses on broadening the JSON serialization capabilities. Here are the most noticeable changes:

Serializing and deserializing Java IO stream. You can now read and write JSON directly to network streams or files.More control over the encoding of default values. A new annotation defines the encoding of default values for each property separately.Exclusion of null values lets you minimize the resulting JSON by removing null property values and restoring them upon deserialization.Custom polymorphic class discriminators increase readability and add semantics to JSON when working with class hierarchies.

Read this blog post to learn about all these new features, or jump right to the How to try section to start exploring them for yourself.

Start using kotlinx.serialization 1.3

Java IO stream-based JSON serialization

Serialization to Java IO streams and deserialization from them has been requested by our users for quite some time. The corresponding GitHub issue is one of the oldest in our repository and has received a huge number of upvotes.

In kotlinx.serialization 1.3.0, we’re finally presenting the first experimental version of the serialization API for IO streams. With this API, you can decode objects directly from files, network streams, and other data sources without reading the data to strings beforehand. The opposite operation is also available: you can send encoded objects directly to files and other streams in a single API call.

IO stream serialization is currently available only on the JVM platform and for the JSON format.

The API includes two main methods:

Json.decodeFromStream() reads an input stream and deserializes an object of the given type from it.Json.encodeToSteam() serializes an object and sends it into the given output stream.

This is how you can read a JSON object from a URL and write it to a file:

@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)
}
}
}

Note that only UTF-8 streams are currently supported.

This is just the first step, and there is still a lot of work to do. Please give the IO stream serialization API a try in your projects and share your feedback with us on the GitHub issue tracker.

Property-level control over default value encoding

kotlinx.serialization reduces the size of the JSON that results when serializing objects by omitting the default values of object properties. Default values are defined in the class declaration and automatically assigned if the corresponding property is not initialized in the code:

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
}

The JSON strings produced by a default Json configuration won’t contain object properties with default values. When decoding such JSON strings, all omitted properties receive their default values as defined in the class declaration.

However, you can force the library to encode the default values by setting the encodeDefaults property of a Json instance to true:

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)
}

This feature was already available, and in 1.3.0 we’re extending it by adding a new way to fine-tune the serialization of default values: you can now control it at the property level using the experimental @EncodeDefault annotation. This has a higher priority level than the encodeDefaults property and takes one of two possible values:

ALWAYS (default value) encodes a property value even if it is equal to the default.NEVER doesn’t encode the default value regardless of the Json configuration.

@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
}

The encoding of the annotated properties is not affected by encodeDefaults and works as described for all serialization formats, not only JSON.

Excluding null values from serialization

In 1.3.0, we’re introducing another way to reduce the size of the generated JSON strings – omitting null values.

A new JSON configuration property, explicitNulls, defines whether null property values should be included in the serialized JSON string. It’s true by default, so all nulls are stored as the values of their corresponding properties.

@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 »}
}

You can make the resulting JSON shorter by excluding the null properties from serialization: just use a Json instance with explicitNulls = false:

@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 »}

The resulting JSON string contains only non-null properties. 

To deserialize objects from JSON with omitted nulls, you also need  a Json instance with explicitNulls == false. Such configuration sets all omitted nullable properties to null unless they have default values. In this case, the default value is used. This is how the json string from this snippet is decoded:

println(format.decodeFromString<Project>(json))
//Project(name=kotlinx.serialization, language=Kotlin, version=1.3.0, website=null)

Trying to use a Json configuration with explicitNulls == true (the default setting) to decode a JSON string with omitted nulls will result in a MissingFieldException.

Custom polymorphic class discriminators

When it comes to class hierarchies, serialization may get a bit difficult because of the additional need to support polymorphism in serialization operations. There are recommended ways to deal with the serialization of hierarchies: make them sealed, annotate each class in the hierarchy as @Serializable, and so on. This documentation page explains in detail how to handle such cases.

In hierarchy serialization, a useful attribute comes into play – class discriminator. It serves as a key for a property that stores the exact class of the object that was encoded. By default, the discriminator has the name “type” and contains a fully qualified class name of the object being serialized, for example:

@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
}

In previous versions, you could change the discriminator name using the classDiscriminator property of the Json instance. Given the hierarchy above, you could write:

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
}

In 1.3.0, we’re adding a way to set a custom discriminator name for each class hierarchy to enable more flexible serialization. You can do this by marking a class with the new experimental annotation @JsonClassDiscriminator, using the discriminator name as its argument.

For example, you can use a word that somehow identifies the whole hierarchy to which an object belongs:

@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”.
}

A custom discriminator applies to the annotated class and its subclasses. Only one custom discriminator can be used in each class hierarchy.

How to try

To enjoy the new JSON features in kotlinx.serialization 1.3.0, apply the latest Kotlin serialization Gradle plugin (note that its version corresponds to Kotlin in general, not the library):

plugins {
kotlin(« jvm ») version « 1.5.31 » // or kotlin(« multiplatform ») or any other Kotlin plugin
kotlin(« plugin.serialization ») version « 1.5.31 »
}

Then add or update the dependency on the JSON serialization library 1.3.0:

dependencies {
implementation(« org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0 »)
// . . .
}

That’s it, you’re ready to go! Try the new features and let us know what you think of them.

If you run into any trouble

Report issues to the GitHub issue tracker.Look for help in the #serialization channel on the Kotlin Slack (get an invite).

Read more

Library on GitHubkotlinx.serialization guideAPI documentationKotlin 1.5.30 release blog post

By admin

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.