Slide 1

Slide 1 text

What is Kotlin Serialization? (and should I use it?)

Slide 2

Slide 2 text

Kotlinx Serialization

Slide 3

Slide 3 text

@Serializable How does it work?

Slide 4

Slide 4 text

Setup

Slide 5

Slide 5 text

buildscript { ext.kotlin_version = '1.3.31' repositories { google() jcenter() } dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } }

Slide 6

Slide 6 text

apply plugin: 'kotlinx-serialization' dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.1' }

Slide 7

Slide 7 text

val json = Json(configuration = jsonConfig) val jsonConfig = JsonConfiguration.Default val jsonConfig = JsonConfiguration.Stable val json = Json(configuration = jsonConfig)

Slide 8

Slide 8 text

public data class JsonConfiguration constructor( internal val encodeDefaults: Boolean = true, internal val strictMode: Boolean = true, internal val unquoted: Boolean = false, internal val prettyPrint: Boolean = false, internal val indent: String = " ", internal val useArrayPolymorphism: Boolean = false, internal val classDiscriminator: String = "type", internal val updateMode: UpdateMode = UpdateMode.OVERWRITE ) val jsonConfig = JsonConfiguration.Default.copy( encodeDefaults = false, prettyPrint = true )

Slide 9

Slide 9 text

Basic Examples

Slide 10

Slide 10 text

@Serializable data class Player( val firstName: String, val lastName: String, val number: Int ) { "firstName": "Ricky", "lastName": "Vaughn", "number": 99 } json.stringify(Player.serializer(), player) @Serializable

Slide 11

Slide 11 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int ) { "first_name": "Ricky", "last_name": "Vaughn", "number": 99 } json.stringify(Player.serializer(), player)

Slide 12

Slide 12 text

{ "first_name": "Ricky", "last_name": "Vaughn", "number": 99 } Player( firstName = "Ricky", lastName = "Vaughn", number = 99 ) val player = json.parse(Player.serializer(), playerJson)

Slide 13

Slide 13 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int ) [ { "first_name": "Ricky", "last_name": "Vaughn", "number": 99 }, { "first_name": "Jake", "last_name": "Taylor", "number": 7 }, { "first_name": "Willie", "last_name": "Mays Hayes", "number": 0 }, { "first_name": "Pedro", "last_name": "Cerrano", "number": 13 } ] json.stringify(Player.serializer().list, players)

Slide 14

Slide 14 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null ) { "first_name": "Jake", "last_name": "Taylor", "number": 7, "nickname": null } json.stringify(Player.serializer(), player)

Slide 15

Slide 15 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null ) { "first_name": "Jake", "last_name": "Taylor", "number": 7 } val json = Json(JsonConfiguration.Stable.copy(encodeDefaults = false)) json.stringify(Player.serializer(), player)

Slide 16

Slide 16 text

@Required @Transient @Transient Other handy tools

Slide 17

Slide 17 text

@Serializable data class Team( val city: String, val name: String, val roster: List ) { "city": "Toronto", "name": "Blue Jays", "roster": [ { "first_name": "Vladimir", "last_name": "Guerrero Jr.", "number": 27 }, { "first_name": "Marcus", "last_name": "Stroman", "number": 6, }, { "first_name": "Justin", "last_name": "Smoak", "number": 14 } ] } json.stringify(Team.serializer(), team)

Slide 18

Slide 18 text

@Serializable data class Message( val title: String, val data: T ) { "title": "Message title", "data": { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing" } } val message: Message = Message("This is the title", player) json.stringify(Message.serializer(Player.serializer()), message)

Slide 19

Slide 19 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null, val statistics: List )

Slide 20

Slide 20 text

@Serializable @SerialName("Pitching") data class Pitching( override val year: Int, val games: Int, val wins: Int, val losses: Int, val era: Double, val whip: Double, val strikeouts: Int ) : Statistics() @Serializable @SerialName("Hitting") data class Hitting( override val year: Int, val avg: Double, val hits: Int, @SerialName("home_runs") val homeRuns: Int, val rbi: Int, @SerialName("stolen_bases") val stolenBases: Int ) : Statistics() @Polymorphic sealed class Statistics { abstract val year: Int }

Slide 21

Slide 21 text

@Serializable @SerialName("Pitching") data class Pitching( override val year: Int, val games: Int, val wins: Int, val losses: Int, val era: Double, val whip: Double, val strikeouts: Int ) : Statistics() @Serializable @SerialName("Hitting") data class Hitting( override val year: Int, val avg: Double, val hits: Int, @SerialName("home_runs") val homeRuns: Int, val rbi: Int, @SerialName("stolen_bases") val stolenBases: Int ) : Statistics() @Polymorphic sealed class Statistics { abstract val year: Int } @Polymorphic

Slide 22

Slide 22 text

val json = Json( configuration = JsonConfiguration.Default, context = SerializersModule { polymorphic(Statistics::class) { Pitching::class with Pitching.serializer() Hitting::class with Hitting.serializer() } } )

Slide 23

Slide 23 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null, val statistics: List ) { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "statistics": [ { "type": "Pitching", "year": 2003, "games": 28, "wins": 12, "losses": 7, "era": 4.39, "whip": 1.3, "strikeouts": 186 } ] } json.stringify(Player.serializer(), player)

Slide 24

Slide 24 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null, val position: Position ) enum class Position(val id: Int) { PITCHER(1), CATCHER(2), FIRST_BASE(3), SECOND_BASE(4), THIRD_BASE(5), SHORTSTOP(6), LEFT_FIELD(7), CENTRE_FIELD(8), RIGHT_FIELD(9), DESIGNATED_HITTER(0) }

Slide 25

Slide 25 text

enum class Position(val id: Int) { PITCHER(1), CATCHER(2), FIRST_BASE(3), SECOND_BASE(4), THIRD_BASE(5), SHORTSTOP(6), LEFT_FIELD(7), CENTRE_FIELD(8), RIGHT_FIELD(9), DESIGNATED_HITTER(0) } { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "position": "PITCHER" } json.stringify(Player.serializer(), player)

Slide 26

Slide 26 text

Custom Serializers

Slide 27

Slide 27 text

@Serializer(forClass = Position::class) object PositionSerializer : KSerializer { override val descriptor = StringDescriptor.withName("Position") override fun serialize(encoder: Encoder, obj: Position) { encoder.encodeInt(obj.id) } override fun deserialize(decoder: Decoder): Position { val id = decoder.decodeInt() return Position.values().first { it.id == id } } }

Slide 28

Slide 28 text

@Serializer(forClass = Position::class) object PositionSerializer : KSerializer { override val descriptor = StringDescriptor.withName("Position") override fun serialize(encoder: Encoder, obj: Position) { encoder.encodeInt(obj.id) } override fun deserialize(decoder: Decoder): Position { val id = decoder.decodeInt() return Position.values().first { it.id == id } } }

Slide 29

Slide 29 text

@Serializer(forClass = Position::class) object PositionSerializer : KSerializer { override val descriptor = StringDescriptor.withName("Position") override fun serialize(encoder: Encoder, obj: Position) { encoder.encodeInt(obj.id) } override fun deserialize(decoder: Decoder): Position { val id = decoder.decodeInt() return Position.values().first { it.id == id } } }

Slide 30

Slide 30 text

@Serializable(with = HandednessSerializer::class) enum class Position(val id: Int) { PITCHER(1), CATCHER(2), FIRST_BASE(3), SECOND_BASE(4), THIRD_BASE(5), SHORTSTOP(6), LEFT_FIELD(7), CENTRE_FIELD(8), RIGHT_FIELD(9), DESIGNATED_HITTER(0) } { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "position": 1 } json.stringify(Player.serializer(), player)

Slide 31

Slide 31 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null, val position: Position, val bats: Handedness, val throws: Handedness ) @Serializable sealed class Handedness { object Right : Handedness() object Left : Handedness() object Switch : Handedness() }

Slide 32

Slide 32 text

@Serializable sealed class Handedness { object Right : Handedness() object Left : Handedness() object Switch : Handedness() } { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "position": 1, "bats": { }, "throws": { } } json.stringify(Player.serializer(), player)

Slide 33

Slide 33 text

@Serializer(forClass = Handedness::class) object HandednessSerializer : KSerializer { override fun serialize(encoder: Encoder, obj: Handedness) = when (obj) { is Handedness.Left -> encoder.encodeString("Left") is Handedness.Right -> encoder.encodeString("Right") is Handedness.Switch -> encoder.encodeString("Switch") } override fun deserialize(decoder: Decoder): Handedness = when (decoder.decodeString()) { "Left" -> Handedness.Left "Right" -> Handedness.Right "Switch" -> Handedness.Switch else -> Handedness.Right } }

Slide 34

Slide 34 text

@Serializable(with = HandednessSerializer::class) sealed class Handedness { object Right : Handedness() object Left : Handedness() object Switch : Handedness() } { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "position": 1, "bats": "Left", "throws": "Right" } json.stringify(Player.serializer(), player)

Slide 35

Slide 35 text

@Serializable data class Player( @SerialName("first_name") val firstName: String, @SerialName("last_name") val lastName: String, val number: Int, val nickname: String? = null, @Serializable(with = DateSerializer::class) @SerialName("birth_date") val birthDate: Date ) { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing", "birth_date": "02/01/85" } json.stringify(Player.serializer(), player)

Slide 36

Slide 36 text

Fantastic. But does this work with Retrofit?

Slide 37

Slide 37 text

https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter

Slide 38

Slide 38 text

dependencies { implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.4.0' } val contentType = MediaType.get("application/json") val baseUrl = "https://my-json-server.typicode.com/physphil/kotlin-serialization-db/" val retrofit = Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(Json.asConverterFactory(contentType)) .build() https://my-json-server.typicode.com/

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

@Test fun `test that retrofit works!`() = runBlocking { val expected = listOf(...) val response = service.getPlayers() val players = response.body() assertThat(players).isEqualTo(expected) } interface PlayerService { @GET("players") suspend fun getPlayers(): Response> }

Slide 41

Slide 41 text

Gotchas

Slide 42

Slide 42 text

gradle clean Things to look out for...

Slide 43

Slide 43 text

Quit stalling, Phil. Should I use it or not?

Slide 44

Slide 44 text

Pros https://medium.com/stanwood/save-my-ass-benchmark-of-json-deserializers-on-android-28341c1e82df

Slide 45

Slide 45 text

Cons

Slide 46

Slide 46 text

Final Decision

Slide 47

Slide 47 text

https://github.com/Kotlin/kotlinx.serialization https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/examples.md https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter Want to learn more?

Slide 48

Slide 48 text

Thanks!