Skip to content

Latest commit

 

History

History
464 lines (330 loc) · 13 KB

builtin-classes.md

File metadata and controls

464 lines (330 loc) · 13 KB

Builtin classes

This is the second chapter of the Kotlin Serialization Guide. In addition to all the primitive types and strings, serialization for some classes from the Kotlin standard library, including the standard collections, is built into Kotlin Serialization. This chapter explains the details.

Table of contents

Primitives

Kotlin Serialization has the following ten primitives: Boolean, Byte, Short, Int, Long, Float, Double, Char, String, and enums. The other types in Kotlin Serialization are composite—composed of those primitive values.

Numbers

All types of integer and floating-point Kotlin numbers can be serialized.

@Serializable
class Data(
    val answer: Int,
    val pi: Double
)                     

fun main() {
    val data = Data(42, PI)
    println(Json.encodeToString(data))
}

You can get the full code here.

Their natural representation in JSON is used.

{"answer":42,"pi":3.141592653589793}

Long numbers

Long integers are serializable, too.

@Serializable
class Data(val signature: Long)

fun main() {
    val data = Data(0x1CAFE2FEED0BABE0)
    println(Json.encodeToString(data))
}

You can get the full code here.

By default they are serialized to JSON as numbers.

{"signature":2067120338512882656}

Long numbers as strings

The JSON output from the previous example will get decoded normally by Kotlin Serialization running on Kotlin/JS. However, if we try to parse this JSON by native JavaScript methods, we get this truncated result.

JSON.parse("{\"signature\":2067120338512882656}")
▶ {signature: 2067120338512882700} 

The full range of a Kotlin Long does not fit in the JavaScript number, so its precision gets lost in JavaScript. A common workaround is to represent long numbers with full precision using the JSON string type. This approach is optionally supported by Kotlin Serialization with LongAsStringSerializer, which can be specified for a given Long property using the @Serializable annotation:

@Serializable
class Data(
    @Serializable(with=LongAsStringSerializer::class)
    val signature: Long
)

fun main() {
    val data = Data(0x1CAFE2FEED0BABE0)
    println(Json.encodeToString(data))
}

You can get the full code here.

This JSON gets parsed natively by JavaScript without loss of precision.

{"signature":"2067120338512882656"}

The section on Specifying serializers for a file explains how a serializer like LongAsStringSerializer can be specified for all properties in a file.

Enum classes

All enum classes are serializable out of the box without having to mark them @Serializable, as the following example shows.

// The @Serializable annotation is not needed for enum classes
enum class Status { SUPPORTED }
        
@Serializable
class Project(val name: String, val status: Status) 

fun main() {
    val data = Project("kotlinx.serialization", Status.SUPPORTED)
    println(Json.encodeToString(data))
}

You can get the full code here.

In JSON an enum gets encoded as a string.

{"name":"kotlinx.serialization","status":"SUPPORTED"}

Note: On Kotlin/JS and Kotlin/Native, @Serializable annotation is needed for enum class if you want to use it as a root object — i.e. use encodeToString<Status>(Status.SUPPORTED).

Serial names of enum entries

Serial names of enum entries can be customized with the SerialName annotation just like it was shown for properties in the Serial field names section. However, in this case, the whole enum class must be marked with the @Serializable annotation.

@Serializable // required because of @SerialName
enum class Status { @SerialName("maintained") SUPPORTED }
        
@Serializable
class Project(val name: String, val status: Status) 

fun main() {
    val data = Project("kotlinx.serialization", Status.SUPPORTED)
    println(Json.encodeToString(data))
}

You can get the full code here.

We see that the specified serial name is now used in the resulting JSON.

{"name":"kotlinx.serialization","status":"maintained"}

Composites

A number of composite types from the standard library are supported by Kotlin Serialization.

Pair and triple

The simple data classes Pair and Triple from the Kotlin standard library are serializable.

@Serializable
class Project(val name: String)

fun main() {
    val pair = 1 to Project("kotlinx.serialization")
    println(Json.encodeToString(pair))
}  

You can get the full code here.

{"first":1,"second":{"name":"kotlinx.serialization"}}

Not all classes from the Kotlin standard library are serializable. In particular, ranges and the Regex class are not serializable at the moment. Support for their serialization may be added in the future.

Lists

A List of serializable classes can be serialized.

@Serializable
class Project(val name: String)

fun main() {
    val list = listOf(
        Project("kotlinx.serialization"),
        Project("kotlinx.coroutines")    
    )
    println(Json.encodeToString(list))
}  

You can get the full code here.

The result is represented as a list in JSON.

[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}]

Sets and other collections

Other collections, like Set, are also serializable.

@Serializable
class Project(val name: String)

fun main() {
    val set = setOf(
        Project("kotlinx.serialization"),
        Project("kotlinx.coroutines")    
    )
    println(Json.encodeToString(set))
}  

You can get the full code here.

Set is also represented as a list in JSON, like all other collections.

[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}]

Deserializing collections

During deserialization, the type of the resulting object is determined by the static type that was specified in the source code—either as the type of the property or as the type parameter of the decoding function. The following example shows how the same JSON list of integers is deserialized into two properties of different Kotlin types.

@Serializable
data class Data(
    val a: List<Int>,
    val b: Set<Int>
)
     
fun main() {
    val data = Json.decodeFromString<Data>("""
        {
            "a": [42, 42],
            "b": [42, 42]
        }
    """)
    println(data)
}

You can get the full code here.

Because the data.b property is a Set, the duplicate values from it disappeared.

Data(a=[42, 42], b=[42])

Maps

A Map with primitive or enum keys and arbitrary serializable values can be serialized.

@Serializable
class Project(val name: String)

fun main() {
    val map = mapOf(
        1 to Project("kotlinx.serialization"),
        2 to Project("kotlinx.coroutines")    
    )
    println(Json.encodeToString(map))
}  

You can get the full code here.

Kotlin maps in JSON are represented as objects. In JSON object keys are always strings, so keys are encoded as strings even if they are numbers in Kotlin, as we can see below.

{"1":{"name":"kotlinx.serialization"},"2":{"name":"kotlinx.coroutines"}}

It is a JSON-specific limitation that keys cannot be composite. It can be lifted as shown in the Allowing structured map keys section.

Unit and singleton objects

The Kotlin builtin Unit type is also serializable. Unit is a Kotlin singleton object, and is handled equally with other Kotlin objects.

Conceptually, a singleton is a class with only one instance, meaning that state does not define the object, but the object defines its state. In JSON, objects are serialized as empty structures.

@Serializable
object SerializationVersion {
    val libraryVersion: String = "1.0.0"
}

fun main() {
    println(Json.encodeToString(SerializationVersion))
    println(Json.encodeToString(Unit))
}  

You can get the full code here.

While it may seem useless at first glance, this comes in handy for sealed class serialization, which is explained in the Polymorphism. Objects section.

{}
{}

Serialization of objects is format specific. Other formats may represent objects differently, e.g. using their fully qualified names.

Duration

Since Kotlin 1.7.20 the Duration class has become serializable.

fun main() {
    val duration = 1000.toDuration(DurationUnit.SECONDS)
    println(Json.encodeToString(duration))
}

You can get the full code here.

Duration is serialized as a string in the ISO-8601-2 format.

"PT16M40S"

Nothing

By default, Nothing is a serializable class. However, since there are no instances of this class, it is impossible to encode or decode its values - any attempt will cause an exception.

This serializer is used when syntactically some type is needed, but it is not actually used in serialization. For example, when using parameterized polymorphic base classes:

@Serializable
sealed class ParametrizedParent<out R> {
    @Serializable
    data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
}

fun main() {
    println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
}

You can get the full code here.

When encoding, the serializer for Nothing was not used

{"value":42}

The next chapter covers Serializers.