AndroidDev
AndroidDev

Reputation: 21237

Handling Kotlin Serialization MissingFieldException with Retrofit

I'm using kotlinx.serialization in conjunction with retrofit. The json response that I receive will vary in terms of what attributes it will contain. In most cases, the data model in my app has more fields than I will receive in the response. I cannot control this, so I need to handle it in code.

Kotlinx.serialization throws a MissingFieldException in such cases. I know that when using Json.parse you can wrap it in a try-catch block and ignore such errors. But since I am using Retrofit, I don't see a way to use that approach:

WebService.kt

interface WebService {
    @GET("person.json")
    fun getPerson(): Call<MainActivity.Person>
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Serializable
    data class Person(val name: String, val species: String, val missing: String)

    @UnstableDefault
    override fun onCreate(savedInstanceState: Bundle?) {
        val mediaType = "application/json".toMediaTypeOrNull()
        mediaType?.let {
            retrofit = Retrofit.Builder()
                .addConverterFactory(Json.nonstrict.asConverterFactory(it))
                .baseUrl(baseUrl)
                .build()
        }

        webService = retrofit.create(WebService::class.java)

        GlobalScope.launch {
            val person = fetchPerson(webService)
        }
    }

    private suspend fun fetchPerson(webService: WebService): Person {
        return suspendCancellableCoroutine { cont ->
            webService.getPerson()
                .enqueue(object : Callback<Person> {
                    override fun onFailure(call: Call<Person>, t: Throwable) {
                        Log.e(t.toString(), "Unable to get api response")
                        cont.cancel(t)
                    }

                    override fun onResponse(
                        call: Call<Person>,
                        response: Response<Person>
                    ) {
                        if (response.isSuccessful) {
                            response.body()?.let { cont.resume(it) }
                        } else {
                            cont.cancel(IOException("${response.code()}: ${response.errorBody()}"))
                        }
                    }
                })
        }
    }
}

The json response (in this made up example) intentionally omits the 'missing' field:

{"name":"me", "species":"superhuman"}

Since that json does not contain the missing field from the data class, the app crashes and throws the MissingFieldException. I'm wondering how to avoid this issue in the Retrofit case.

Thanks for any help.

Upvotes: 7

Views: 7545

Answers (3)

iadcialim24
iadcialim24

Reputation: 4015

For others who has already made the property as nullable and still get the issue, set explicitNulls to false in your JsonBuilder

@OptIn(ExperimentalSerializationApi::class)
val CustomJson = Json {
    ignoreUnknownKeys = true
    isLenient = true
    explicitNulls = false
}

// usage
CustomJson.decodeFromString<Class>(jsonString)

enter image description here

Upvotes: 0

Waseem
Waseem

Reputation: 61

Starting from kotlinx.serialization-1.3.0, you can create the Json object as Json { explicitNulls = false }. This will help in serializing response with varying fields without throwing the MissingFieldException and without the need of passing default values.

Reference

Upvotes: 3

Md. Asaduzzaman
Md. Asaduzzaman

Reputation: 15423

Actually it can't create Person object from json as your Person class constructor wants 3 value. You have to fulfill this requirement to create object.

One possible solution to resolve this is to use default value in Kotlin like this:

data class Person(
    var name: String="", 
    var species: String="", 
    var missing: String="") 

Another solution is to use multiple constructor with varying no of parameters but since you mentioned it may differ over time, so this solution may not handy. Thanks

Upvotes: 14

Related Questions