Handling Kotlin Serialization MissingFieldException with Retrofit
Asked Answered
C

3

7

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.

Calypso answered 11/10, 2019 at 2:9 Comment(2)
Could you please try to add default value in your Person data model. Hope this will help youBorscht
That worked, but I'm hoping for something that won't require me to set default values across all of my data models. The real world application is MUCH more complex. If you post this as an answer, and I don't get a better one, then I'll accept this. Thank you very much!Calypso
B
14

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

Borscht answered 11/10, 2019 at 2:33 Comment(2)
Thank you. I may have to resort to this eventually. I'm hoping to find a setting that will relax this requirement. For example, Gson has no such requirement. If I do wind up using this, I will accept this answer.Calypso
Sure, no problem. You can also try vararg for similar types of parameter, though it loses key->value facilitiesBorscht
S
3

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

Scarcity answered 28/9, 2021 at 17:35 Comment(0)
B
0

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

Bourgogne answered 18/6, 2023 at 4:8 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.