Dmitry
Dmitry

Reputation: 369

retrofit + gson deserializer: return inside array

I have api that return json:

{"countries":[{"id":1,"name":"Australia"},{"id":2,"name":"Austria"}, ... ]}

I write model class (Kotlin lang)

data class Country(val id: Int, val name: String)

And I want do request using retorift that returning List < Models.Country >, from "countries" field in json

I write next:

interface DictService {

    @GET("/json/countries")
    public fun countries(): Observable<List<Models.Country>>

    companion object {
        fun create() : DictService {
            val gsonBuilder = GsonBuilder()
            val listType = object : TypeToken<List<Models.Country>>(){}.type
            gsonBuilder.registerTypeAdapter(listType, CountriesDeserializer)
            gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)

            val service = Retrofit.Builder()
                    .baseUrl("...")
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
                    .build()
            return service.create(DictService::class.java)
        }
    }

    object CountriesDeserializer : JsonDeserializer<List<Models.Country>> {
        override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<Models.Country>? {
            val res = ArrayList<Models.Country>()
            if(json!=null) {
                val countries = json.asJsonObject.get("countries")
                if (countries.isJsonArray()) {
                    for (elem: JsonElement in countries.asJsonArray) {
                        res.add(Gson().fromJson(elem, Models.Country::class.java))
                    }
                }
            }
            return null;
        }

    }

}

But I get error: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $ CountriesDeserializer code dont execute even! What they want from me?

Maybe I need write my own TypeAdapterFactory?

I dont want use model class like

class Countries {
   public List<Country> countries;
}

Upvotes: 0

Views: 340

Answers (3)

miensol
miensol

Reputation: 41688

If your intention is to simplify the interface and hide the intermediate wrapper object I guess the simplest thing to do is to add an extension method to the DictService like so:

interface DictService {

    @GET("/json/countries")
    fun _countries(): Observable<Countries>
}

fun DictService.countries() = _countries().map { it.countries }
data class Countries(val countries: List<Country> = listOf())

Which can then be used as follows:

val countries:Observable<List<Country>> = dictService.countries()

Upvotes: 2

voddan
voddan

Reputation: 33849

I would use Jackson for this task. Try this https://github.com/FasterXML/jackson-module-kotlin

val mapper = jacksonObjectMapper()
data class Country(val id: Int, val name: String)

// USAGE:
val country = mapper.readValue<Country>(jsonString)

Upvotes: 0

Dmitry
Dmitry

Reputation: 369

I found the way:

object CountriesTypeFactory : TypeAdapterFactory {
    override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T>? {
        val delegate = gson?.getDelegateAdapter(this, type)
        val elementAdapter = gson?.getAdapter(JsonElement::class.java)

        return object : TypeAdapter<T>() {
            @Throws(IOException::class)
            override fun write(outjs: JsonWriter, value: T) {
                delegate?.write(outjs, value)
            }

            @Throws(IOException::class)
            override fun read(injs: JsonReader): T {
                var jsonElement = elementAdapter!!.read(injs)
                if (jsonElement.isJsonObject) {
                    val jsonObject = jsonElement.asJsonObject
                    if (jsonObject.has("countries") && jsonObject.get("countries").isJsonArray) {
                        jsonElement = jsonObject.get("countries")
                    }
                }
                return delegate!!.fromJsonTree(jsonElement)
            }
        }.nullSafe()
    }
}

But it is very complex decision, I think, for such problem. Are there another one simpler way?

Another one: I found bug in my initial code from start meassage!!! It works fine if replace List by ArrayList!

Upvotes: 0

Related Questions