Migrate from GSON to Moshi in Android

Migrate from GSON to Moshi in Android

I received a call from client this morning. They accidentally put the UAT version Android APK in production. This caused multiple devices stop functioning with the not updated server.

Even though it is client's fault, the application should not crash. I decided to investigate the cause.

import com.google.gson.annotations.SerializedName

data class Response(@SerializedName("param1") val param1: String,
                    @SerializedName("param2") val param2: String,
                    @SerializedName("param3") val param3: String) {
    val param3Int
        get() = param3.toIntOrNull()
}

param3 is newly added to the upcoming release. So the server in production’s response does not contain param3 and accessing param3Int caused a NullPointerException.

However, I am using Retrofit2 to handle the REST request which has error handler to handle the error during retrieving JSON. It turns out it is not called because GSON decides to set the field to null instead of throwing exception for a missing field.

To solve this problem, we have two solutions:

  • Write an Annotation and JsonDeserializer to check for missing field.
  • Make all the fields in the data class nullable.

The first one introduces a lot of unnecessary code to code base. The second default the purpose of using Kotlin to write null safe code instead of checking null everywhere.

Then, I find out Moshi.

What is Moshi?

Moshi is a JSON serialization library written by square who creates retrofit and okhttp.

Installation

dependencies {
    implementation "com.squareup.retrofit2:converter-moshi:2.3.0"
    implementation "com.squareup.moshi:moshi:1.6.0"
    implementation "com.squareup.moshi:moshi-kotlin:1.6.0"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:1.6.0"
}

Refactor

init {
    val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
    val retrofit = Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()
}
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Response(@Json(name = "param1") val param1: String,
                    @Json(name = "param2") val param2: String,
                    @Json(name = "param3") val param3: String) {
    val param3Int
        get() = param3.toIntOrNull()
}

We replace GsonConverterFactory with MoshiConverterFactory, @SerializedName with @Json and add @JsonClass. Then, it is done.

That’s it?

Yes, that’s it. Now Moshi can parse JSON based on Kotlin declaration to determine a field can be nullable or not.

While GSON is a more well-known library, the releases seems to be stale. Only minor changes is released over the years and we see there is no plan to integrate with Kotlin.

If you are writing Android app with Kotlin (You absolute should use Kotlin over Java in Android), you should use Moshi instead of GSON for JSON serialization library.