Kotlin's strongest parsing library-kotlin-serialization to write network request converters

Kotlin's strongest parsing library-kotlin-serialization to write network request converters

Serialization is JetBrains' open source parsing library. It has multiple platforms and supports multiple parsing formats. It is also one of the most powerful serialization parsing libraries on Kotlin.

Documentation:

Unique Advantages of Serialization

  1. Non-null check/non-null coverage, will not overwrite the default value of construction parameters, solve the problem of back-end returning null coverage fields
  2. Solve the problem of generic erasure, directly serialize/deserialize List/Map/Pair, etc.
  3. Non-reflective high performance
  4. Support multiple formats (ProtoBuf/CBOR/Custom)
  5. Annotation data class automatically generates sequencers, manually constructs sequencers
  6. The code is simple and elegant
  7. Dynamic analysis

Serialization uses Kotlin's Type instead of Java. Currently, only the Net network request library supports its converter. It is possible to specify any generic analysis result: access to the document .

Use Net to write the most elegant request code

installation

Project build.gradle

classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" //The code can be copied with the same version number as the Kotlin plugin

module build.gradle

apply plugin: 'kotlinx-serialization' Implementation "org.jetbrains.kotlinx: kotlinx-serialization-json: 1.2.0" Copy Code

JSON usage

Key category
JsonSingleton object, used to parse JSON configuration
Json.DefaultSingleton object configured by default

function

Json's functions are divided into inline generic functions/normal generic functions.

  1. Inline generic functions can automatically resolve object types. In general, you can use the function directly
  2. Ordinary generics require a specified parser. Should be encapsulated for parsing
functiondescription
encodeToStringSerialize to string
encodeToJsonElementSerialize to JsonElement
decodeFromStringDeserialize from string
decodeFromJsonElementDeserialized from JsonElement

Example

//Serialize val json:String = Json.encodeToString(Model( "Peng Yuyan" , 23 )) //deserialization Val JSON = "" "{" name ":" Eddie "," Age ":} 33 is" "" Val Model Json.decodeFromString = <the Model> (JSON) copying the code

Serialization

  1. Only attributes support serialization (i.e. include setters and getters)
  2. The default value will not be serialized, even if it is a nullable attribute
@Serializable class Project ( val name: String, val language: String) fun main () { val data = Project( "kotlinx.serialization" , "Kotlin" ) println(Json.encodeToString( data )) } Copy code

Deserialization

@Serializable class Project ( val name: String, val language: String) fun main () { val data = Project( "kotlinx.serialization" , "Kotlin" ) println(Json.encodeToString( data )) } Copy code
  1. Private constructor, expose other constructors, so Serialization will serialize the exposed constructor properties
  2. When the data type field has more attributes than JSON, serialization will fail, unless the extra attributes have default values (
    @Required
    JSON matching field is mandatory)
  3. If there is a loop structure field during serialization, it will cause a stack overflow. It is recommended that you ignore this field
  4. When the JSON field overwrites the attribute value, the default value of the attribute value is a function, which will not be called
  5. JSON overwrites properties with default values will be wrong

annotation

annotationModification positiondescription
@TransientFieldIgnore field
@RequiredFieldThe parameters that force the default value must also match the JSON field
@SerialNameFieldThe modifier class is the name of the person who specifies the sequence, and the modifier field is the name specified in JSON
@JsonNamesFieldYou can specify multiple names for the field, and the field name will not be invalid
@SerialInfoclassAllow the compiler to save the comment information to the SerialDescriptor, use
getElementAnnotations
@SerializerclassThe specified parameter is the target class, and the target class creates a serializer with its modified class

model

  • Unsigned types/inline classes are not supported temporarily
  • Singleton objects do not support serialization (Unit is a singleton), the serialization result is
    {}
  • The properties of the parent class will not be serialized
  • Objects will be serialized only when they are assigned (the assignment of null will also participate), and there will be no default values (unless it is modified with @Required).
  • None of the delegated fields/Val fields will participate in serialization. Only when you have set/get at the same time will it be serialized (the construction parameter allows val)
  • The fields of the private constructor will also be serialized. If the construction parameter is not val or var, it will not participate in serialization.
@Serializable class Model ( var name: String, var age: Int ) { var height: Long ? = 23 //No assignment will never be serialized } Copy code

Or declare the sequencer directly in the file

@file: UseSerializers (DateAsLongSerializer :: class ) Copy the code

Custom sequencer,

BoxSerializer
Custom serializer

@Serializable (with class :: = BoxSerializer) Data class Box < T > ( Val Contents: T) Copy Code

When the class cannot be modified, we can use modifiers for the sequencer

@Serializer

@Serializer (forClass Project :: = class) Object ProjectSerializer copy the code

Not only construction parameters will be sequenced, only properties with getter/setter functions will participate

The parent class is modified by @Serializable, and its subclasses must also be modified

@Serializable open class Project ( val name: String) class OwnedProject (name: String, val owner: String): Project(name) fun main () { val data : Project = OwnedProject( "kotlinx.coroutines" , "kotlin" ) println(Json.encodeToString( data )) val data = OwnedProject( "kotlinx.coroutines" , "kotlin" ) //throw an exception } Copy code

Only sealed classes allow abstract attributes, if not sealed classes are not allowed

@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.coroutines" , "kotlin" ) println(Json.encodeToString( data )) } Copy code

type
The value of the key can use the attribute name

@Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String): Project() Copy code

When the collection is polymorphic, one will be added by default

type
Field

Copy code

Json configuration

use

Json{}
Construct Json instance object

val format = Json {prettyPrint = true } @Serializable data class Project ( val name: String, val language: String) fun main () { val data = Project( "kotlinx.serialization" , "Kotlin" ) println(format.encodeToString( data )) } Copy code
OptionsdescriptionDefaults
prettyPrint: BooleanGenerate typeset JSONfalse
prettyPrintIndent: StringSpecify the indented string for typesettingfalse
isLenient: BooleanAllow non-double quotes to wrap key valuesfalse
ignoreUnknownKeys: BooleanMissing fields in data classes that allow deserializationfalse
useAlternativeNames: BooleanWhether to enable @JsonName annotation, if you don t use this annotation, it is recommended to disable this configurationtrue
coerceInputValues: BooleanEmpty and unknown enumerations will not override the property default valuefalse
allowStructuredMapKeys: BooleanEnable Map serialization. By default, Map cannot be serializedfalse
allowSpecialFloatingPointValues: BooleanAllow serialization
Double.NaN
This special floating-point type
false
classDiscriminator = "#class"Use the attribute name as the value,
#class
Key name for custom ide
serializersModule = moduleForDate
encodeDefaults: BooleanWhether to serialize the default value to support the attribute (non-abstract) serialization of the parent classfalse

Enable Map serialization (Map cannot be serialized by default)

val format = Json {allowStructuredMapKeys = true } @Serializable data class Project ( val name: String) fun main () { val map = mapOf( Project( "kotlinx.serialization" ) to "Serialization" , Project( "kotlinx.coroutines" ) to "Coroutines" ) println(format.encodeToString(map)) } Copy code

Class description

In this way, a field will be automatically added to the serialized JSON to describe the class information of the data model

val format = Json {classDiscriminator = "#class" } //key @Serializable sealed class Project { abstract val name: String } @Serializable @SerialName( "owned" ) //value class OwnedProject ( override val name: String, val owner: String): Project() fun main () { val data : Project = OwnedProject( "kotlinx.coroutines" , "kotlin" ) println(format.encodeToString( data )) //{"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} } Copy code

Allows to specify floating point values

val data = Data( Double .NaN) the println (format.encodeToString ( Data )) //{ "value": NaN3} copy the code

Enable default

Set up

coerceInputValues
If true, the default value will be used instead of overwriting the field when the type is not nullable, but the JSON value is empty.

At the same time, when there is an unknown enumeration type, the default value will be used

JsonElement

Use function

Json.parseToJsonElement
Parse out a JsonElement object, which is not deserialized

Subclassdescription
JsonPrimitivePrimitive types in Kotlin
JsonArrayA collection of JsonElement
JsonObjectA Map collection of JsonElement
JsonNullEmpty type

JsonPrimitive
It has a series of basic type acquisition functions, please call to return String
content
function.

functiondescription
jsonPrimitiveReturn to the original type
jsonObjectReturn to Map
jsonArrayReturn collection
jsonNullReturn Null
fun main () { val element = Json.parseToJsonElement( """ { "name": "kotlinx.serialization", "forks": [{"votes": 42}, {"votes": 9000}, {}] } """ ) val sum = element .jsonObject[ "forks" ]!! .jsonArray.sumOf {it.jsonObject[ "votes" ]?.jsonPrimitive?.int ?: 0 } println(sum) } Copy code

Build JSON

Provide top-level DSL functions to build JSON

fun main () { val element = buildJsonObject { put( "name" , "kotlinx.serialization" ) putJsonObject( "owner" ) { put( "name" , "kotlin" ) } putJsonArray( "forks" ) { addJsonObject { put( "votes" , 42 ) } addJsonObject { put( "votes" , 9000 ) } } } println(element) } Copy code

KSerializer

KSerializer belongs to the serializer in Serialization, an interface specification that includes serialization and deserialization.

The sequencer can be created in the following ways

  1. Bound data class

    @Serializable (with ColorAsStringSerializer :: = class) class Color ( Val rgb: Int ) Copy the code
  2. Specify objects on the sequencer

    //NOT @Serializable class Project ( val name: String, val language: String) @Serializer(forClass = Project::class) object ProjectSerializer //The sequencer can use copy code without any code logic
  3. Function parameters

    public Fun <T> decodeFromJsonElement (Deserializer: DeserializationStrategy < T >, Element: JsonElement ) : T Copy Code
  4. Specifies the sequencer used by the class of the current file

    @file:UseSerializers (DateAsLongSerializer:: class ) @Serializable class programminglanguage ( Val name: String, Val stableReleaseDate: a Date) Copy the code

Automatically generate sequencer

Each is

@Serializable
There is a singleton function in the decorated class
serializer()
Return a
KSerializer<T>
Serialization

  • So you cannot declare to create a
    serializer
    function
  • There is a default sequencer for primitive types:
    Int.serializer()
// Serialization public interface SerializationStrategy < in T > { public val descriptor: SerialDescriptor public fun serialize (encoder: Encoder , value: T ) } // Deserialization public interface DeserializationStrategy < T > { public val descriptor: SerialDescriptor public fun deserialize (decoder: Decoder ) : T } //Serializer public interface KSerializer < T >: SerializationStrategy < T >, DeserializationStrategy < T > { override val descriptor: SerialDescriptor } Copy code

Encoding and decoding

Encoding and decodingdescription
EncoderSerialized Root Interface
CompositeEncoder
JsonEncoderUsed for JSON serialization
DecoderDeserialize root interface
CompositeDecoder
JsonDecoderUsed for JSON deserialization

Encoder can be used

encodeStructure
To start coding manually

There is a loop inside the decoder that keeps calling

decodeElementIndex
Start to decode, encounter
CompositeDecoder.DECODE_DONE
Stop loop

If the data is stored in order, we can use it directly

decodeSequentially
Function to terminate the loop

override fun deserialize (decoder: Decoder ) : Color = decoder.decodeStructure(descriptor) { var r = -1 var g = -1 var b = -1 if (decodeSequentially()) { //sequential decoding protocol r = decodeIntElement(descriptor, 0 ) g = decodeIntElement(descriptor, 1 ) b = decodeIntElement(descriptor, 2 ) } else while ( true ) { when ( val index = decodeElementIndex(descriptor)) { 0 -> r = decodeIntElement(descriptor, 0 ) 1 -> g = decodeIntElement(descriptor, 1 ) 2 -> b = decodeIntElement(descriptor, 2 ) CompositeDecoder.DECODE_DONE -> break else -> error( "Unexpected index: $index " ) } } require(r in 0. .255 && g in 0. .255 && b in 0. .255 ) Color((r shl 16 ) or (g shl 8 ) or b) } Copy code

Types of

There are many top-level functions defined in Serialization to create serializers: BuiltinSerializers.kt

Sequencer
PairSerializer
MapEntrySerializer
TripleSerializer
*ArraySerializer
ListSerializer
SetSerializer
MapSerializer
LongAsStringSerializer

Basically include all data types, deserialization does not require the use of serializers to support types by default. Enumeration serialization and deserialization do not require redundant processing

Use generic type inference to quickly generate the corresponding sequencer

val stringToColorMapSerializer: KSerializer<Map<String, Color>> = serializer() println(stringToColorMapSerializer.descriptor) Copy code

Serialization parses Map, the key is always a string, even a number

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

Generic

@Serializable class Box < T > ( val contents: T) fun main () { val boxedColorSerializer = Box.serializer(Color.serializer()) println(boxedColorSerializer.descriptor) } Copy code
  • The number of generics will cause the request to be passed to
    serializer
    The number of parameters, each generic parameter should have its own sequencer

Json serialization

JsonTransformingSerializer
Belongs to Json parsing implementation. If we need to customize the serializer parsing JSON, we can inherit the implementation function of this class

public abstract class JsonTransformingSerializer < T: Any > ( private val tSerializer: KSerializer<T> ): KSerializer<T> { //Deserialization protected open fun transformDeserialize (element: JsonElement ) : JsonElement = element //Serialization protected open fun transformSerialize (element: JsonElement ) : JsonElement = element } Copy code

Filter out a value (default value will be filtered by default)

In order to facilitate use and save memory, the parser generally uses singleton objects.

fun main () { val data = Project( "kotlinx.serialization" , "Kotlin" ) println(Json.encodeToString( data )) //using plugin-generated serializer println(Json.encodeToString(ProjectSerializer, data )) //using custom serializer } @Serializable class Project ( val name: String, val language: String) object ProjectSerializer: JsonTransformingSerializer<Project>(Project.serializer()) { override fun transformSerialize (element: JsonElement ) : JsonElement = //If the value of the key "language" is "Kotlin", filter out JsonObject(element.jsonObject.filterNot { (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" }) } Copy code

Polymorphic serialization provides a function to return a concrete serializer

object ProjectSerializer: JsonContentPolymorphicSerializer<Project>(Project:: class ) { override fun selectDeserializer (element: JsonElement ) = when { "owner" in element.jsonObject -> OwnedProject.serializer() else -> BasicProject.serializer() } } Copy code