Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Data serialization isn’t exactly a groundbreaking topic, and it’s likely everyone reading this deals with it on a regular basis. Typically we use an external library to help us parse a server response body into its model representation, some of the most common being GSON, Moshi or Jackson. But did you know that Kotlin has its own library to help us with this task?

Join us for this deep dive into the Kotlin Serialization library. You’ll find out how to set it up in your project and get taken through some operation examples, with a focus on the important Android specific implementation details, including integration with Retrofit. This session will help you determine if Kotlin Serialization is the right choice for your next project.

Phil Shadlyn

July 19, 2019
Tweet

More Decks by Phil Shadlyn

Other Decks in Programming

Transcript

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

  2. Kotlinx Serialization

  3. @Serializable How does it work?

  4. Setup

  5. buildscript { ext.kotlin_version = '1.3.31' repositories { google() jcenter() }

    dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } }
  6. apply plugin: 'kotlinx-serialization' dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.1' }

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

    val jsonConfig = JsonConfiguration.Stable val json = Json(configuration = jsonConfig)
  8. 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 )
  9. Basic Examples

  10. @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
  11. @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)
  12. { "first_name": "Ricky", "last_name": "Vaughn", "number": 99 } Player( firstName

    = "Ricky", lastName = "Vaughn", number = 99 ) val player = json.parse(Player.serializer(), playerJson)
  13. @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)
  14. @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)
  15. @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)
  16. @Required @Transient @Transient Other handy tools

  17. @Serializable data class Team( val city: String, val name: String,

    val roster: List<Player> ) { "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)
  18. @Serializable data class Message<T>( val title: String, val data: T

    ) { "title": "Message title", "data": { "first_name": "Ricky", "last_name": "Vaughn", "number": 99, "nickname": "Wild Thing" } } val message: Message<Player> = Message("This is the title", player) json.stringify(Message.serializer(Player.serializer()), message)
  19. @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<Statistics> )
  20. @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 }
  21. @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
  22. val json = Json( configuration = JsonConfiguration.Default, context = SerializersModule

    { polymorphic(Statistics::class) { Pitching::class with Pitching.serializer() Hitting::class with Hitting.serializer() } } )
  23. @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<Statistics> ) { "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)
  24. @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) }
  25. 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)
  26. Custom Serializers

  27. @Serializer(forClass = Position::class) object PositionSerializer : KSerializer<Position> { 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 } } }
  28. @Serializer(forClass = Position::class) object PositionSerializer : KSerializer<Position> { 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 } } }
  29. @Serializer(forClass = Position::class) object PositionSerializer : KSerializer<Position> { 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 } } }
  30. @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)
  31. @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() }
  32. @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)
  33. @Serializer(forClass = Handedness::class) object HandednessSerializer : KSerializer<Handedness> { 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 } }
  34. @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)
  35. @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)
  36. Fantastic. But does this work with Retrofit?

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

  38. 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/
  39. None
  40. @Test fun `test that retrofit works!`() = runBlocking { val

    expected = listOf<Player>(...) val response = service.getPlayers() val players = response.body() assertThat(players).isEqualTo(expected) } interface PlayerService { @GET("players") suspend fun getPlayers(): Response<List<Player>> }
  41. Gotchas

  42. gradle clean Things to look out for...

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

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

  45. Cons

  46. Final Decision

  47. 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?

  48. Thanks!