Reputation: 369
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
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
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
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