Bios90
Bios90

Reputation: 811

Retrofit+Gson+RxJava throw error on parsing failed

I got some strange behaviour when using Retrofit+Gson+RxJava

Here is my retrifit object

Retrofit.Builder()
            .baseUrl(Constants.Urls.URL_BASE)
            .addConverterFactory(GsonConverterFactory.create(Gson()))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(client)
            .build()

here is my data class

data class User(
        val id:Int,
        val email:String,
        val name:String)

here is my retrofit interface

    @Multipart
    @POST(Constants.Urls.URL_LOGIN)
    fun makeLogin(@PartMap map: Map<String, String?>): Observable<Model_User>

When login is successful all works as it should, but i get strange behavoiur when instead of json object in reponse i got string error e.g.

{
    "error": {
        "code": 400,
        "message": "Wrong password"
    }
}

Observable calls a success in subscription, with User object. And this object has null values on fields that cant be null.

my_api.makeLogin(map)
            .subscribe(
                {
                    //Here i got User(id = null,email = null,name = null)
                },
                {
                    //But i need to call error here on parsing failed
                })

What should i do to throw error before onNext called? Is it possible to make retrofit throw error when Gson parsing fails instead of emmiting empty Object?

Upvotes: 0

Views: 263

Answers (1)

Mohru
Mohru

Reputation: 743

Firstly, this is a bit strange that when a login error occurrs, the resposnse from your server does not trigger the observable's error. Probably there are some backend issues?

Secondly, unfortunately Gson is not null-safe, as it uses reflection for parsing the objects, so even not nullable field can have null value if none is provided (see eg. this article). They even have a feature request to provide a mechanism for forcing an exception then requested field is not in the json (stuck there for 5 years).

For now, probably the best option to handle this is to create two different models: an API model with all fields nullable, and domain model (the User model that you already have) and flatMap the the mapping result. Eg:

my_api.makeLogin(map)
        .flatMap { apiUser ->
            try {
                val user = apiUser.mapToDomain() //throw exception while mapping if field is missing
                Single.just(user)
            }catch(e: Exception){
                Single.error<User>(IllegalStateException())
            }
        }
        .subscribe( ... )

Upvotes: 1

Related Questions