Safely Navigating the Transition: From Gson to kotlinx.serialization

Mahmoud Afarideh
ProAndroidDev
Published in
5 min readDec 29, 2023

--

Introduction

kotlinx.serialization

Embarking on the journey from Gson to kotlinx.serialization is like upgrading your app to a more Kotlin-friendly and optimized serialization experience. Let’s dive into why we chose kotlinx.serialization, the challenges we faced during the transition, and the solutions that made the migration smooth and reliable. 🚀✨

Embracing kotlinx.serialization

In the world of Android development, where Kotlin rules, kotlinx.serialization is like the perfect sidekick. This library not only blends seamlessly but also follows a Kotlin-first approach, aligning beautifully with the language’s philosophy.🌐

Kotlin-Centric Approach

Say goodbye to the old ways of Gson; kotlinx.serialization puts Kotlin front and center. It taps into Kotlin’s native features like data classes and sealed classes, making data serialization and deserialization a breeze.🔄

Compile-Time Safety

The real charm here is the heightened compile-time safety. It’s like having a superhero cape that reduces runtime errors related to serialization and deserialization, giving me more confidence in my code.🦸‍♂️

Performance Gains

And the perks don’t stop there! kotlinx.serialization brings some serious performance gains. Optimized for Kotlin, it promises efficient serialization and deserialization, giving a nice boost to the overall application performance.⚡️

Problems and Challenges

Photo by Danka & Peter on Unsplash

Default Value Issues

Transitioning from Gson to kotlinx.serialization brought its set of challenges around default values. The different approaches required careful handling to ensure consistent serialization and deserialization.🚧

//kotlinx.serialization
@Serializable
data class SomeDto(
// Default value should be provided
@SerialName("some_optional_key")
val optionalKey: Int = 0,
)

// Gson
data class SomeDto(
@SerializedName("some_optional_key")
val optionalKey: Int,
)

Nullable Values and Platform Type Issues

Dealing with nullable values and platform types needed full attention. The migration called for a thoughtful strategy to prevent any unexpected runtime issues.💡

//kotlinx.serialization
@Serializable
data class SomeDto(
// Exception would be thrown if the key is not present in the json
// even if optionalKey is not accessed in the code
@SerialName("some_key")
val key: String,
)

// Gson
data class SomeDto(
// No exception would be thrown if optionalKey is not accessed in the code
@SerializedName("some_key")
val key: String,
)

Lack of Documentation and Testing

Navigating through insufficient documentation and testing gaps for API response deserialization added a bit of spice to the migration process.📚🔍

Photo by Adrian Swancar on Unsplash

Solutions Employed

Leveraging IDE Tools

To make life easier, I dived into the arsenal of IDE tools in Android Studio. Using regex and Find/Replace, I seamlessly integrated kotlinx.serialization annotations alongside Gson annotations.🧰

Find/Replace using regex
// Before
@SerializedName("field_name")
val fieldName: String

// After
@SerializedName("field_name")
@SerialName("field_name")
val fieldName: String

Feature Flags for Safe Merging

Photo by Jaye Haych on Unsplash

Ensuring a smooth merging of changes into the main branch, I used feature flags. Check out more about feature flags here. This allowed for a gradual introduction of kotlinx.serialization while keeping Gson as the primary serializer until I was sure the transition was a success.🚩

if (useKtxSerialization) {
// Use kotlinx.serialization
} else {
// Use Gson
}

Combined Converter for Retrofit

Photo by Vardan Papikyan on Unsplash

Implementing a combined converter for Retrofit was a game-changer. This converter supported both Gson and kotlinx.serialization, making the incremental migration of endpoints a breeze while keeping our app stable.🚗💨

// Retrofit setup with combined converter
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
CombinedConverterFactory(
KotlinxSerializationConverterFactory.create(),
GsonConverterFactory.create()
)
)
.build()

Polymorphic Serialization

Polymorphic serialization in kotlinx.serialization is a breeze. Let’s say we have a hierarchy of shapes:

@Serializable
@Polymorphic
sealed class Shape

@Serializable
@SerialName("CircleType")
data class Circle(val radius: Double) : Shape()

@Serializable
@SerialName("SquareType")
data class Square(val sideLength: Double) : Shape()

Now, when serializing a list of shapes:

val shapes: List<Shape> = listOf(Circle(5.0), Square(4.0))
val json = Json.encodeToString(shapes)

// JSON
[
{"type":"CircleType","radius":5.0},
{"type":"SquareType","sideLength":4.0}
]

Writing Tests for Complex Objects

To tackle challenges with custom serializers and polymorphics, I went all out with comprehensive tests for complex serializable objects. This proactive approach, helped me catch issues early in the migration process.🧪

    private val jsonString = """         
{
"some_object": {
"type": "SOME_TYPE",
"payload" : {
"some_key": "some_value"
}
}
}""".trimIndent()


// Example test for complex serializable object
@Test
fun testComplexObjectSerialization() {
// Test logic using kotlinx.serialization
// Assert statements
val someObject = json.decodeFromString<SomeClass>(jsonString)
assertEquals(
expectedModel, someObject
)
}

Fallback to Gson for Stability

Photo by Diana Polekhina on Unsplash

To keep our app sailing smoothly, I introduced Gson as a fallback for kotlinx.serialization. This dual support allowed me to confidently navigate unexpected challenges during the migration.⚓️

// Fallback logic in case kotlinx.serialization fails
try {
// Use kotlinx.serialization
} catch (e: SerializationException) {
// Monitor exception
// Fallback to Gson
}

Incremental Stability Monitoring

To ensure stability incrementally, I enlisted monitoring tools to track issues arising from kotlinx.serialization usage. This proactive approach helped us identify and resolve challenges as they surfaced.🔍📈

Photo by Luke Chesser on Unsplash

Conclusion

In conclusion, the transition from Gson to kotlinx.serialization wasn’t without its quirks, but with some informed decisions and a strategic approach, We successfully navigated the migration. Leveraging IDE tools, using feature flags, writing comprehensive tests, and keeping Gson as a fallback ensured a safe and incremental transition. The lessons learned and strategies I employed serve as a friendly guide for others embarking on a similar journey. May your migration endeavors be as smooth and rewarding! 🚀✨

--

--