For quite some time, Kotlin didn’t have its own serialization/deserialization library. So you have to make do with Java libraries, the most commonly used is probably Gson. However, many of Kt’s most powerful features, such as parameter defaults and attribute delegates, were removed and reduced to Javaer (yes, kotlin, Java traitors). There are third-party serialization libraries that support Kt, such as Moshi, but they don’t work well, and Gson is used to bushi. If you want to know more about Moshi, look it up. Personally, I think Moshi is going to die soon after the official library comes out.

Before introducing today’s main characters, let’s review the use of Gson in KT, which is no different from Java:

// The reason for using data classes is that objects can be printed directly
data class Student(val name: String, val score: Int = 80)

fun main(a){
    val gosn = Gson()
    val Icarus = Student("Icarus".99)
    println(gson.toJson(Icarus))//{"name":"Icarus","score":99}
    val Icarus2 = gson.fromJson("""{"name":"Icarus","score":99}""", Student::class.java)
    println(Icarus2)//Student(name=Icarus, score=99)
    println(Icarus == Icarus2)//true
    
    // The following is a case where the parameters have default values
    val SoharaMitsuki = Student("SoharaMitsuki")
    println(gson.toJson(SoharaMitsuki ))//{"name":"SoharaMitsuki","score":80}
    valSoharaMitsuki2 = gson. FromJson ("""{"name":"SoharaMitsuki"}""", Student::class.java) println (SoharaMitsuki2)//{"name":"SoharaMitsuki2","score":0}
}
Copy the code

Note that the score attribute we defined has a default value, so I’m going to go straight to the conclusion. The object generated using the default value is a Json string and everything is fine, but Gson deserializes the Student object using the Json string with no value given. The score attribute value is 0, and the default value is not used. Gson first looks for the constructor in the class definition, which doesn’t have the property in its argument list. If it doesn’t, it uses the Java black magic Unsafe class to create objects directly. data class Student WithInits(val name: String, val score: Int){

Val firstName by lazy {name.split(" ")[0]} /** * An attribute followed by lazy has no background field and is initialized without a space in memory to store the value. / val lastName by lazy {name.split(" ")[1]} / val lastName by lazy {name.split(" ")[1]}Copy the code

}

Because the attributes followed by lazy can be computed at runtime, they are ignored during serialization to reduce the LENGTH of the Json. Since the lazy initialization property is an empty reference at the time the object is generated, and the corresponding property of the object is null when Gson retrieves it from the JSON string, Gson treats KClass as JavaClass, and the lazy code information is lost. If you must use both Gson and lazy initialization, search “@poko” on Baidu for details.

Kotlinx. Serialization first configures Gradle:

/ / build. Gradle plugins {id 'org. Jetbrains. Kotlin. The JVM' version '1.4.20' id '. Org. Jetbrains kotlin. Plugin. Serialization ' Version '1.4.20'} Repositories {// Artifacts are also available on Maven Central JCenter ()} dependencies { Implementation "org. Jetbrains. Kotlinx: kotlinx - serialization - json: 1.0.1"}Copy the code

For JSON, we use the Json.encodeToString extension to encode the data. It serializes the serializable object as its parameters behind the scenes and encodes it as a JSON string. Let’s start with the class that describes the project and try to get its JSON representation.

@Serializable data class Project(val name: String, val language: String) fun main() { val data = Project("kotlinx.serialization", "Kotlin") println(json.encodeToString (data)) // Print {"name":"kotlinx.serialization","language":"Kotlin"} val data = Json.decodeFromString<Project>(""" {"name":"kotlinx.serialization","language":"Kotlin"} """) println(data)//Project(name=kotlinx.serialization, language=Kotlin) }Copy the code

Again, not only the data classes can be serialized, but the classes can be printed directly when deserialized. If there is no parameter, use the Serializer provided by the system. Background field serialization only serializes properties of classes that have backup fields, so properties with getters/setters but no background fields are not serialized, nor are delegated properties serialized.

@Serializable class Project( // name is a property with backing field -- serialized var name: String ) { var stars: Int = 0 // property with a backing field -- serializedval path: String // getter only, no backing field -- not serialized get() = "kotlin/$name" var id by ::name // delegated property -- not serialized } fun  main() { val data = Project("kotlinx.serialization").apply { stars = 9000 } println(Json.encodeToString(data)) //{"name":"kotlinx.serialization","stars":9000} }Copy the code

If we wanted to define the Project class, make it take a path string, and then deconstruct it into corresponding attributes, we might be tempted to write something like the following:

@Serializable 
class Project(path: String) {
    val owner: String = path.substringBefore('/')    
    val name: String = path.substringAfter('/')    
}
Copy the code

This class cannot compile because the @serialIZABLE annotation requires that all parameters of the class’s main constructor be attributes. A simple solution is to define a private primary constructor using the attributes of the class, and then convert the required constructors into auxiliary constructors.

@Serializable 
class Project private constructor(val owner: String, val name: String) {
    constructor(path: String) : this(
        owner = path.substringBefore('/'),    
        name = path.substringAfter('/')
    )                        

    val path: String
        get() = "$owner/$name"  
}

fun main() {
    println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
    //{"owner":"kotlin","name":"kotlinx.serialization"}
}
Copy the code

Path has no background fields and will not be serialized. Data validation Another case is that you might want to introduce a primary constructor parameter without attributes, validating it before storing its value into attributes. To make it serializable, replace it with a property in the main constructor and move validation to init {… } block:

@Serializable
class Project(val name: String) {
    init {
        require(name.isNotEmpty()) { "name cannot be empty" }
    }
}

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":""}
    """)//Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
    println(data)
}
Copy the code

The default value

Default properties are automatically populated when deserialized, and are not written to JSON when serialized, again to save space and bandwidth. In most real-world scenarios, this configuration can reduce visual clutter and save on the amount of data to serialize.

0@Serializable 
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)//Project(name=kotlinx.serialization, language=Kotlin)
    
    val data1 = Project("kotlinx.serialization")
    println(Json.encodeToString(data1))//{"name":"kotlinx.serialization"}
}
Copy the code

A similar case is where nullable properties default to NULL

@Serializable
class Project(val name: String, val renamedTo: String? = null)

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))//{"name":"kotlinx.serialization"}
}
Copy the code

When an optional property exists in the input, the corresponding initializer for that property is not called. This feature is designed to improve performance, so be careful not to rely on side effects in the initializer.

fun computeLanguage(): String {
    println("Computing")
    return "Kotlin"
}

@Serializable 
data class Project(val name: String, val language: String = computeLanguage())
 
fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)//Project(name=kotlinx.serialization, language=Kotlin)
}
Copy the code

Because the language attribute is specified in the input, the “compute” string is not seen in the output.

The value of the @required attribute must be explicitly specified.

@Serializable 
data class Project(val name: String, @Required val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""    
        {"name":"kotlinx.serialization"}    
    """)
    println(data)//Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required, but it was missing
}
Copy the code

The @TRANSIENT attribute is not serialized and cannot be specified when deserialized.

@Serializable 
data class Project(val name: String, @Transient val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""        
        {"name":"kotlinx.serialization","language":"Kotlin"}    
    """)/**
    *Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException:
    *Unexpected JSON token at offset 60: Encountered an unknown key 'language'.
    *Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
    */
    println(data)
}
Copy the code

Kt serialization framework strictly supports Kt’s type system, so the following code has an exception:

@Serializable 
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":null}
    """)//Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found.
//Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values.
    println(data)
}
Copy the code

Nesting doll serialization

Serializable classes can reference other classes in their serializable properties. The referenced class must also be marked @serializable

@Serializable
class Project(val name: String, val owner: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner)
    println(Json.encodeToString(data))//{"name":"kotlinx.serialization","owner":{"name":"kotlin"}}
}
Copy the code

Do not compress repeated references

@Serializable
class Project(val name: String, val owner: User, val maintainer: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner, owner)
    println(Json.encodeToString(data))
    //{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}
}
Copy the code

Kotlin serialization is designed to encode and decode pure data. It does not support rebuilding arbitrary object graphs with duplicate object references. Try serializing an instance that references the same object twice, writing the string twice. So don’t have circular references to instances, that will burst the stack.

A generic class

The generic classes in Kotlin provide type polymorphism behavior that is enforced at compile time by the Kotlin serialization. For example, consider a generic serializable class Box. The actual type we get in JSON depends on the actual compile-time type parameter specified for the Box.

@Serializable
class Box<T>(val contents: T)

@Serializable
class Data(
    val a: Box<Int>,
    val b: Box<Project>
)

fun main() {
    val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
    println(Json.encodeToString(data))
    //{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}
}
Copy the code

Key alias

By default, the name of the property used in the encoded representation (JSON in our example) is the same as the name in the source code. The name used for serialization is called the sequence name and can be changed using @serialName. For example, we can use language attributes in our source code and use abbreviated sequence names.

@Serializable
class Project(val name: String, @SerialName("lang") val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
    //{"name":"kotlinx.serialization","lang":"Kotlin"}
}
Copy the code