Android parse JSON using JacksonAnnotation - android

I'm trying to parse a JSON without results.
I've a response like following:
{
"0": {
"id": "4",
"nome": "Zero Gravity",
"id_stop": "0"
},
"1": {
"id": "540",
"nome": "First Name",
"id_stop": "111"
}
}
The problem is that it's not a list with a defined id, but the id is a progressive integer that varies according to the possible answers. How do I do proper parsing? I tried building a response like this:
here my service
#GET("{path}")
suspend fun getResponse(
#Path(value = "path", encoded = true) path: String,
#Query(value = "cmd") alias: String? = "cmd",
#Query(value = "id_where") idWhere: String,
): Response<ArrayList<APIResponse>>
Here APIResponse
data class APIResponse(
#JsonProperty("id")
val id: String?,
#JsonProperty("nome")
val nome: String?,
#JsonProperty("id_stop")
val idStop: String?,
) {
fun toDomain(): API {
return API( id, nome, idStop
)
}
}
here my repository
suspend fun getAPIResponse(from: String) : API {
val response = service.getResponse(path = basePath, idWhere = where)
return response.body()?.map {
it.toDomain()
}
}
This isn't the solution though, because I can't get a complete answer but I always have a single item with all fields null. Should I use a HashMap? how could i solve?

Related

android-kotlin JsonObject JsonArray send request data via POST

I want to send data request via post to server I want to know How can I add data in array
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("detail")
val detail: List<String?>
)
Request
data class CityRequest(
#SerializedName("listCity")
val listCity: List<City?>
)
Response
data class CityResponse(
#SerializedName("code")
val code: String?,
#SerializedName("status")
val status: Boolean?,
#SerializedName("message")
val message: String?
)
API Server
#Headers("Content-Type: application/json")
#POST("city")
suspend fun sendCityContent(#Body listCity: CityRequest?):
Call<CityResponse?>
Connect Service
I don't know how I can add information to this section in question.
private suspend fun sendDataCity(city: List<city?>) {
val retrofit = clientCity
val sendDataToServer = retrofit?.create(CityService::class.java)
val call = sendDataToServer?.sendCityContent(CityRequest(city))
call?.enqueue(object : Callback<CityResponse?> {
override fun onResponse(
call: Call<CityResponse?>, response: Response<CityResponse?>) {
val getResponse = response.body()
Timber.tag("SALE_CITY: ").d("code: %s", getResponse?.code)
Timber.tag("SALE_CITY: ").d("status: %s", getResponse?.status)
Timber.tag("SALE_CITY: ").d("message: %s", getResponse?.message)
}
override fun onFailure(call: Call<CityResponse?>, t: Throwable?) {
t?.printStackTrace()
}
})
}
JSON Simple
{
"city": [
{
"cityId": 1,
"answer": [
"1"
]
},
{
"questionId": 2,
"answer": [
"2.1",
"2.2"
]
}
]}
What do I have to do next?
Can you have a sample add data in array for me?
Things I want
cityId = 1
detail = "1.1", "1.2"
cityId = 2
detail = "2.1", "2.2"
thank you
One issue i can see with your request is the key is different from what you are sending might be different check that. it should be city not listCity as given.
data class CityRequest(
#SerializedName("city")
val city: List<City?>
)
and your city class should have these keys answer which you have mentioned as details
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("answer")
val answer: List<String?>
)
I guess you are just sending with wrong keys that might be the reason the server is not accepting the request. make the above change it should work post if you get error.

how to flatten nested JSON into single class using retrofit and gson converter?

I have a nested JSON like this from Server, as you can see there is a nested data in location
{
"id": "18941862",
"name": "Pizza Maru",
"url": "https://www.zomato.com/jakarta/pizza-maru-1-thamrin?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"location": {
"address": "Grand Indonesia Mall, East Mall, Lantai 3A, Jl. M.H. Thamrin No. 1, Thamrin, Jakarta",
"locality": "Grand Indonesia Mall, Thamrin",
"city": "Jakarta",
"city_id": 74,
"latitude": "-6.1954467635",
"longitude": "106.8216102943",
"zipcode": "",
"country_id": 94,
"locality_verbose": "Grand Indonesia Mall, Thamrin, Jakarta"
},
"currency": "IDR"
}
I am using retrofit and using gson converter. usually I need to make 2 data class for something like this to map JSON into POJO. so I need to make Restaurant class and also Location class, but I need to flatten that json object into single Restaurant class, like this
data class Restaurant : {
var id: String
var name: String
var url: String
var city: String
var latitude: Double
var longitude: Double
var zipcode: String
var currency: String
}
how to do that if I am using retrofit and gson converter ?
java or kotlin are ok
This solution is a silver bullet for this problem and cannot be appreciated enough.
Take this Kotlin file first:
/**
* credits to https://github.com/Tishka17/gson-flatten for inspiration
* Author: A$CE
*/
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD)
annotation class Flatten(val path: String)
class FlattenTypeAdapterFactory(
private val pathDelimiter: String = "."
): TypeAdapterFactory {
override fun <T: Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {
val delegateAdapter = gson.getDelegateAdapter(this, type)
val defaultAdapter = gson.getAdapter(JsonElement::class.java)
val flattenedFieldsCache = buildFlattenedFieldsCache(type.rawType)
return object: TypeAdapter<T>() {
#Throws(IOException::class)
override fun read(reader: JsonReader): T {
// if this class has no flattened fields, parse it with regular adapter
if(flattenedFieldsCache.isEmpty())
return delegateAdapter.read(reader)
// read the whole json string into a jsonElement
val rootElement = defaultAdapter.read(reader)
// if not a json object (array, string, number, etc.), parse it
if(!rootElement.isJsonObject)
return delegateAdapter.fromJsonTree(rootElement)
// it's a json object of type T, let's deal with it
val root = rootElement.asJsonObject
// parse each field
for(field in flattenedFieldsCache) {
var element: JsonElement? = root
// dive down the path to find the right element
for(node in field.path) {
// can't dive down null elements, break
if(element == null) break
// reassign element to next node down
element = when {
element.isJsonObject -> element.asJsonObject[node]
element.isJsonArray -> try {
element.asJsonArray[node.toInt()]
} catch(e: Exception) { // NumberFormatException | IndexOutOfBoundsException
null
}
else -> null
}
}
// lift deep element to root element level
root.add(field.name, element)
// this keeps nested element un-removed (i suppose for speed)
}
// now parse flattened json
return delegateAdapter.fromJsonTree(root)
}
override fun write(out: JsonWriter, value: T) {
throw UnsupportedOperationException()
}
}.nullSafe()
}
// build a cache for flattened fields's paths and names (reflection happens only here)
private fun buildFlattenedFieldsCache(root: Class<*>): Array<FlattenedField> {
// get all flattened fields of this class
var clazz: Class<*>? = root
val flattenedFields = ArrayList<Field>()
while(clazz != null) {
clazz.declaredFields.filterTo(flattenedFields) {
it.isAnnotationPresent(Flatten::class.java)
}
clazz = clazz.superclass
}
if(flattenedFields.isEmpty()) {
return emptyArray()
}
val delimiter = pathDelimiter
return Array(flattenedFields.size) { i ->
val ff = flattenedFields[i]
val a = ff.getAnnotation(Flatten::class.java)!!
val nodes = a.path.split(delimiter)
.filterNot { it.isEmpty() } // ignore multiple or trailing dots
.toTypedArray()
FlattenedField(ff.name, nodes)
}
}
private class FlattenedField(val name: String, val path: Array<String>)
}
Then add it to Gson like this:
val gson = GsonBuilder()
.registerTypeAdapterFactory(FlattenTypeAdapterFactory())
.create()
Retrofit.Builder()
.baseUrl(baseUrl)
...
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Using your example, you can get the pojo parsed like this:
// prefer constructor properties
// prefer val over var
// prefer added #SerializedName annotation even to same-name properties:
// to future proof and for easier proguard rule config
data class Restaurant(
#SerializedName("id") val id: String,
#SerializedName("name") val name: String,
#SerializedName("url") val url: String,
#Flatten("location.city") val city: String,
#Flatten("location.latitude") val latitude: Double,
#Flatten("location.longitude") val longitude: Double,
#Flatten("location.zipcode") val zipcode: String,
#SerializedName("currency") var currency: String
)
You can even write a path down an array, e.g #Flatten("friends.0.name") = get first friend's name.
For more info, visit this repo
Note, however, that I stripped down the TypeAdapter to only read/consume json objects. You can implement write() if you want to use it to write json too.
You are welcome.

Gson custom deserializer when it contains object or json string

How I can I write custom deserializer for same object? Or create 2 Objects (one which contains raw json string and another - Object)?
I'm getting "all device list" from server, and "updated device list" from server. Problem is that when I getting "all device list" I'm getting "Sensor", "Services", "Tail" as objects, but when getting "updated device list" that fields comes as strings (json strings). I will add photos to clarify issue.
All devices response:
[All devices] https://lh3.googleusercontent.com/lzhJXviqtW2l0pLHkJ9-2VBck9hnmREdfTlWTMF8BYTklbXR90jDe2AZbG9sHHPynCXh3HWlFQBsFBiL_oke-AMy-79zIqUsW8HVT7CrulA_KKyItARfwbRxWm8LWBspEriJEsfT4_AgNR7uceyO9pH4VAIm9LBBcuvoFOMJmvSt-y85dgqPld3WJ0RwIlQOBv3rqiw6Ci4QOC983LpiJAKgTgshsQeGNKUWnBIbzkJr87vtF54XOf4PsdAqv3IuRczQSeNLllPTIEaz6rylCotcgANWShMcpm-hQdlrt7E0VBygGTZGLW4hoOQn2F4DWH8p-QH2pEb7JKtEopha0q5cyISbK6AQSYHKMpYb7E8I_FKoxbQIEsvvjDPwGezZlloI1r6HnRHDpAaL8a2eF3yZ3ABTORHS8fa5UY___nvE1cppgzhuK9a1pFFX_A7yZQN_zQNM58i5hW6q0MhawbfFyY8NXBtTOujxz_NIg4_qP3Eu58Eb-wwz17pxYfpAbPRiDXrQDgaXPSmOrJ_txvdzP910UcxHZqFNiW_k3DXUN2tzCY2XSuyyZgc2eX-ZWaE0tevqsvdiCJSd2OvNBC8ihmZUTT0mreyZy-ii8xi2uHjN0KCtvmAkpy-XlKU1lddoxlwj_IrZqbSH_UPdqF4sa5tEahQ=w598-h1336-no
Updated devices response:
[updated devices] https://lh3.googleusercontent.com/-5VlMyTbTCmLcc14oWbaE5QViCio1V3NlcXQrrApZatE8K3CnW4flCGTbpncp_rbFnhjYq49WBqKBBD4g8TBwbHvZe1qsUZSW4JaTr1ZVJFCiLz1fl6SMredI7XnYhbm85q4-RxwxaEP90X6QAR7JOqZICk1QWTS0KxTp3XckHP45ca02LHicNO4TuB72_M1-vx4FMoAZQ42vSrnKZsHBqprJnsthS3ZoG9_v72xgjXPFRb144sRb0PPn6L9VbphZPbBhcg7WU68LK6eZgqyjIByxnkAlh-tsOLsWfMoze1Df93x9OI6OqOao8cphZD_bot-AViErtnc_vyNLHaDniJ6G6gUdDgco6rNCgyPvhGIcPbKf-wWdB8ENQ47HeJVxuJ0E9EE7rGRFhPchR1FxGM2nHewyoicds6uS2eCVpiKjtnen6TuTDfx3aH9NRUzL3thvN_9Jengh_XsyMPpvhGGHAUEcpYraHEXdtC25X9t5zpB1FhLzDiWVIZwhwihOCVI-5J-VBi7JqCOm9eaagYFunZSQI6U3u1qK1SZZfCarzxm_1dCYUECBNAUCdb_HfHM24m9-D8_R77t9DNrMPgisPtOonkqORJzTtHsz-FjyxXAHbp72urUGrHLLlZnRRsrpj4NFdLX_DuXwp5k3SiUlnWTLHk=w2214-h1312-no
[All devices model] https://lh3.googleusercontent.com/lRHdw7nVVz05dG57znq_Vzwwc9zFgEmeS_kQsrqcY2u6OOkcknrpMSytte14jOqmRH_829RAitVc3HVQL2St3g8iSh7STm9XNxuBTJkxAo9OPjbv6l02s02jT35IXWo2qhGy0hiiD4uBfAlYdyy-fjXGCAe4wMLvJYPiDUc9mY73Tf3rxRw5TJ1-XW11hyE3oeHj5OkMzET7_mMoKhro3Bs97HzdDOWO2_5QHAYeqwIuQNJ8NfjU1HTeozdOF_NuAk2nZF8YajHX_VJ4FZtOhovTltZGWJseQ2RzM3gsmd2nt5-obE3msOxPYXf3R9lYlolryRNpBFGHWOrR9xj-6QfuxIOHJOWhQAkkNEAOB7emcvvFzgSQkj0Xkoro2nZCkPH-wagSAJFm-cMhOjapdwhvvlUEjaOh9dw29RBT7jfYH5r2f7i2KELBxksG1uIVo9PcHjg0hvgC0xsw2WMLWFlDnWetngJxmyl6U8pM8NNLghm5CxFaMAKnygnN_4F0aCjfQEeBX-XaC5ss4_1fDQnifw7UcnhBH7n5VZqyYiK1jNqPYDGFJS1J8FJPkXmZsc8uOtAXIa0t58ZOrQ5jHeAKsIlrlj-eM_HP7hM9iQi0VCxjaRmonD_nc6ueKGjyj4xDm9b4uAXnO1B3krVfGAstaJ_RvHw=w552-h652-no
[updated devices model] https://lh3.googleusercontent.com/8uIPKFBfwEfGg7t_KlIB8xJrI1RPJW8WhozKIa7enNlGf7AMZqF0r8qz3FPZ6IzSlyfI3PFk6OFIskPpxzep5_cHudRK5LhpbHdsZkplM8aCoeOSO7-DG8bgTQ__-iEzpmhSG4HORmLauA80L6tEh3SJxbOPoKlconsj7cXWT2uZcPD9xYAjBYHtMp7wrOZXEeAHJPqLoonxIVPsQ-CGTmhVDjQW33YUqhUytDY1FlJcnNCRQVUTRsw5NasxwvCkDVdhFBFlDNyVXX6HN_GL-ZBLNmxrOQyxz_Q7_D7e_sGVp9iw0iQIABr4ogk-9chsY8F0iNjdrD67BxGXaExRN9Nog-TuHgDEf1uLgHUJi6TZRyoalalF-q1Rf6h3Xj3mKj56bSfgk8Z25W3fToOQBMwNEwcYIy8c4bzDjMzvS8san5XkRDIrilN7-iRwJQjH-Q43cgLeSg7wTBCFrqeRPblRCe79A1dVTEnlkHl9aPmp8aW9ih0b5yMcmJjK40BwqQu0sBplzBL_oKDeooVxQ7Yp-sAfa9XKHB-s4WiKbU5fAfURt_jsRO5JtHCeiyjVw9D2RiCZ_fbzTTZGMv4ysJNIYU9oADJLdl-zHTbGzpNggd-KxOWnoDffdHlQkH5nB_FeH11hMbkvdjpFarblpMjh_hhh5dM=w681-h763-no
Json where I'm getting Object
[{"id":"68","title":"testRecall","items":[{"id":992,"alarm":0,"name":"CloudMM","online":"offline","time":"2018-10-28 23:30:58","timestamp":1540806358,"acktimestamp":1540806358,"lat":0,"lng":0,"course":0,"speed":0,"altitude":0,"icon_type":"icon","icon_color":"red","icon_colors":{"moving":"green","stopped":"red","offline":"red","engine":"yellow"},"icon":{"id":10,"user_id":null,"type":"icon","order":3,"width":46,"height":64,"path":"images\/device_icons\/v2\/objects2a_38.png","by_status":"0"},"power":"-","address":"-","protocol":"osmand","driver":"-","driver_data":{"id":null,"user_id":null,"device_id":null,"name":null,"rfid":null,"phone":null,"email":null,"description":null,"created_at":null,"updated_at":null},"sensors":[],"services":[],"tail":[],"distance_unit_hour":"kph","unit_of_distance":"km","unit_of_altitude":"mt","unit_of_capacity":"lt","stop_duration":"0h","moved_timestamp":0,"engine_status":null,"detect_engine":"gps","engine_hours":"gps","total_distance":0,"device_data":{"id":992,"user_id":70,"current_driver_id":null,"timezone_id":null,"traccar_device_id":992,"icon_id":10,"icon_colors":{"moving":"green","stopped":"red","offline":"red","engine":"yellow"},"active":1,"deleted":0,"name":"CloudMM","imei":"665544332211","fuel_measurement_id":1,"fuel_quantity":"0.00","fuel_price":"0.00","fuel_per_km":"0.00","sim_number":"","device_model":"","plate_number":"","vin":"","registration_number":"","object_owner":"","additional_notes":"","expiration_date":null,"sim_expiration_date":"0000-00-00","sim_activation_date":"0000-00-00","installation_date":"0000-00-00","tail_color":"#33cc33","tail_length":5,"engine_hours":"gps","detect_engine":"gps","min_moving_speed":6,"min_fuel_fillings":10,"min_fuel_thefts":10,"snap_to_road":0,"gprs_templates_only":0,"valid_by_avg_speed":"1","parameters":"[\"batterylevel\",\"satellites\",\"hdop\",\"sequence\",\"distance\",\"totaldistance\",\"motion\",\"valid\",\"enginehours\"]","currents":null,"created_at":"2018-10-29 05:34:27","updated_at":"2019-02-27 07:58:31","forward":{"active":"1","ip":"198.121.31.32","port":"6000","protocol":"TCP"},"stop_duration":"0h","pivot":{"user_id":70,"device_id":992,"group_id":68,"current_driver_id":null,"active":1,"timezone_id":null},"traccar":{"id":"992","name":"CloudMM","uniqueId":"665544332211","latestPosition_id":"1","lastValidLatitude":null,"lastValidLongitude":null,"other":"<info><batterylevel>23<\/batterylevel><satellites>0<\/satellites><hdop>0<\/hdop><sequence>6<\/sequence><distance>0<\/distance><totaldistance>0<\/totaldistance><motion>false<\/motion><valid>false<\/valid><enginehours>0<\/enginehours><\/info>","speed":"0.00","time":"2018-10-29 09:45:45","device_time":"2018-10-29 09:45:45","server_time":"2018-10-29 09:45:58","ack_time":"2018-10-29 09:45:58","altitude":null,"course":null,"power":null,"address":null,"protocol":"osmand","latest_positions":null,"moved_at":null},"icon":{"id":10,"user_id":null,"type":"icon","order":3,"width":46,"height":64,"path":"images\/device_icons\/v2\/objects2a_38.png","by_status":"0"},"sensors":[],"services":[],"driver":null,"users":[{"id":70,"email":"tomas#gpswox.com"}],"lastValidLatitude":0,"lastValidLongitude":0,"latest_positions":null,"icon_type":"icon","group_id":68,"user_timezone_id":null,"time":"2018-10-29 09:45:45","course":0,"speed":0}},{"id":1099,"alarm":0,"name":"testRecalObject","online":"offline","time":"Not connected","timestamp":0,"acktimestamp":0,"lat":0,"lng":0,"course":0,"speed":0,"altitude":0,"icon_type":"arrow","icon_color":"red","icon_colors":{"moving":"green","stopped":"yellow","offline":"red","engine":"yellow"},"icon":{"id":0,"user_id":null,"type":"arrow","order":1,"width":25,"height":33,"path":"assets\/images\/arrow-ack.png","by_status":"0"},"power":"-","address":"-","protocol":"-","driver":"-","driver_data":{"id":null,"user_id":null,"device_id":null,"name":null,"rfid":null,"phone":null,"email":null,"description":null,"created_at":null,"updated_at":null},"sensors":[],"services":[],"tail":[],"distance_unit_hour":"kph","unit_of_distance":"km","unit_of_altitude":"mt","unit_of_capacity":"lt","stop_duration":"0h","moved_timestamp":0,"engine_status":null,"detect_engine":"gps","engine_hours":"gps","total_distance":0,"device_data":{"id":1099,"user_id":70,"current_driver_id":null,"timezone_id":null,"traccar_device_id":1099,"icon_id":0,"icon_colors":{"moving":"green","stopped":"yellow","offline":"red","engine":"yellow"},"active":1,"deleted":0,"name":"testRecalObject","imei":"oooopaaa","fuel_measurement_id":1,"fuel_quantity":"0.00","fuel_price":"0.00","fuel_per_km":"0.00","sim_number":"","device_model":"","plate_number":"","vin":"","registration_number":"","object_owner":"","additional_notes":"","expiration_date":null,"sim_expiration_date":"0000-00-00","sim_activation_date":"0000-00-00","installation_date":"0000-00-00","tail_color":"#63f542","tail_length":0,"engine_hours":"gps","detect_engine":"gps","min_moving_speed":1,"min_fuel_fillings":1,"min_fuel_thefts":1,"snap_to_road":0,"gprs_templates_only":0,"valid_by_avg_speed":"1","parameters":null,"currents":null,"created_at":"2019-07-20 21:46:11","updated_at":"2019-07-20 21:46:11","forward":null,"stop_duration":"0h","pivot":{"user_id":70,"device_id":1099,"group_id":68,"current_driver_id":null,"active":1,"timezone_id":null},"traccar":{"id":"1099","name":"testRecalObject","uniqueId":"oooopaaa","latestPosition_id":null,"lastValidLatitude":null,"lastValidLongitude":null,"other":null,"speed":null,"time":null,"device_time":null,"server_time":null,"ack_time":null,"altitude":null,"course":null,"power":null,"address":null,"protocol":null,"latest_positions":null,"moved_at":null},"icon":{"id":0,"user_id":null,"type":"arrow","order":1,"width":25,"height":33,"path":"assets\/images\/arrow-ack.png","by_status":"0"},"sensors":[],"services":[],"driver":null,"users":[{"id":70,"email":"tomas#gpswox.com"}],"lastValidLatitude":0,"lastValidLongitude":0,"latest_positions":null,"icon_type":"arrow","group_id":68,"user_timezone_id":null,"time":null,"course":0,"speed":0}}]}]
And where I'm getting String
{"items":[{"id":124,"alarm":0,"name":"Demo 1","online":"ack","time":"2019-08-22 03:26:02","timestamp":1566481263,"acktimestamp":0,"lat":55.675715,"lng":12.576404,"course":37,"speed":0,"altitude":1,"icon_type":"icon","icon_color":"yellow","icon_colors":{"moving":"green","stopped":"yellow","offline":"red","engine":"yellow"},"icon":{"id":3,"user_id":null,"type":"icon","order":3,"width":46,"height":64,"path":"images/device_icons/v2/objects2a_63_ack.png","by_status":"1"},"power":"-","address":"-","protocol":"homtecs","driver":"Testing","driver_data":{"id":"3","user_id":"1","device_id":"128","device_port":null,"name":"Testing","rfid":"ABC451132","phone":"4","email":"","description":"","created_at":"2017-06-20 19:00:43","updated_at":"2018-10-22 14:38:51"},"sensors":"[{\"id\":\"299\",\"type\":\"satellites\",\"name\":\"Satellites\",\"show_in_popup\":\"0\",\"value\":\"8\",\"val\":\"8\",\"scale_value\":null},{\"id\":\"300\",\"type\":\"satellites\",\"name\":\"Accuracy\",\"show_in_popup\":\"0\",\"value\":\"1.02\",\"val\":\"1.02\",\"scale_value\":null}]","services":"[{\"id\":\"9\",\"name\":\"Service one\",\"value\":\"Days Left (28d.)\",\"expiring\":false},{\"id\":\"19\",\"name\":\"Ignas\",\"value\":\"\\\"Sensor\\\" was not found.\",\"expiring\":true},{\"id\":\"20\",\"name\":\"Ignas w\",\"value\":\"Days Left (1560d.)\",\"expiring\":false},{\"id\":\"21\",\"name\":\"Poiu\",\"value\":\"\\\"Sensor\\\" was not found.\",\"expiring\":true}]","tail":"[{\"lat\":\"55.675024666667\",\"lng\":\"12.5744815\"},{\"lat\":\"55.675158166667\",\"lng\":\"12.574806833333\"},{\"lat\":\"55.675362333333\",\"lng\":\"12.575193666667\"},{\"lat\":\"55.675581666667\",\"lng\":\"12.575788833333\"},{\"lat\":\"55.675698666667\",\"lng\":\"12.5761065\"}]","distance_unit_hour":"kph","unit_of_distance":"km","unit_of_altitude":"mt","unit_of_capacity":"lt","stop_duration":"1min 42s","moved_timestamp":1566481160,"engine_status":null,"detect_engine":"gps","engine_hours":"gps","total_distance":7088342.97,"device_data":{"id":124,"user_id":70,"current_driver_id":3,"timezone_id":null,"traccar_device_id":124,"icon_id":3,"icon_colors":{"moving":"green","stopped":"yellow","offline":"red","engine":"yellow"},"active":1,"deleted":0,"name":"Demo 1","imei":"100000001","fuel_measurement_id":1,"fuel_quantity":"0.00","fuel_price":"0.00","fuel_per_km":"0.00","sim_number":"","device_model":"","plate_number":"","vin":"","registration_number":"","object_owner":"","additional_notes":"","expiration_date":null,"sim_expiration_date":"0000-00-00","sim_activation_date":"0000-00-00","installation_date":"0000-00-00","tail_color":"#33cc33","tail_length":5,"engine_hours":"gps","detect_engine":"gps","min_moving_speed":6,"min_fuel_fillings":10,"min_fuel_thefts":10,"snap_to_road":0,"gprs_templates_only":0,"valid_by_avg_speed":"1","parameters":"[\"sat\",\"hdop\",\"valid\",\"enginehours\",\"rfid\"]","currents":{"geofences":[]},"created_at":"2017-07-04 08:13:53","updated_at":"2018-12-20 08:10:59","forward":null,"stop_duration":"1min 42s","pivot":{"user_id":70,"device_id":124,"group_id":71,"current_driver_id":3,"active":1,"timezone_id":null},"traccar":{"id":"124","name":"Demo 1","uniqueId":"100000001","latestPosition_id":"9275412","lastValidLatitude":"55.675714833333","lastValidLongitude":"12.576404333333","other":"<info><sat>8</sat><hdop>1.02</hdop><valid>true</valid><enginehours>8862443</enginehours><distance>0.46</distance><totaldistance>7088342.97</totaldistance></info>","speed":"0.06","time":"2019-08-22 13:41:02","device_time":"2019-08-22 13:41:02","server_time":"2019-08-22 13:41:03","ack_time":null,"altitude":"1.4","course":"36.8","power":null,"address":null,"protocol":"homtecs","latest_positions":"55.675698666667/12.5761065;55.675581666667/12.575788833333;55.675362333333/12.575193666667;55.675158166667/12.574806833333;55.675024666667/12.5744815;55.6749245/12.574212666667;55.674809333333/12.5739365;55.674687833333/12.5736325;55.674487333333/12.5731665;55.674489666667/12.572678;55.67469/12.5724915;55.6748885/12.572210333333;55.675289/12.571647333333;55.675530666667/12.5713325;55.6757135/12.571215666667","moved_at":"2019-08-22 13:39:20"},"icon":{"id":3,"user_id":null,"type":"icon","order":3,"width":46,"height":64,"path":"images/device_icons/v2/objects2a_63_online.png","by_status":"1"},"sensors":[{"id":"299","user_id":"1","device_id":"124","name":"Satellites","type":"satellites","tag_name":"sat","add_to_history":"0","on_value":null,"off_value":null,"shown_value_by":null,"fuel_tank_name":null,"full_tank":null,"full_tank_value":null,"min_value":null,"max_value":null,"formula":null,"odometer_value_by":null,"odometer_value":null,"odometer_value_unit":"km","temperature_max":null,"temperature_max_value":null,"temperature_min":null,"temperature_min_value":null,"value":"10","value_formula":"0","show_in_popup":"0","unit_of_measurement":"","on_tag_value":null,"off_tag_value":null,"on_type":null,"off_type":null,"calibrations":null,"skip_calibration":null},{"id":"300","user_id":"1","device_id":"124","name":"Accuracy","type":"satellites","tag_name":"hdop","add_to_history":"0","on_value":null,"off_value":null,"shown_value_by":null,"fuel_tank_name":null,"full_tank":null,"full_tank_value":null,"min_value":null,"max_value":null,"formula":null,"odometer_value_by":null,"odometer_value":null,"odometer_value_unit":"km","temperature_max":null,"temperature_max_value":null,"temperature_min":null,"temperature_min_value":null,"value":"0.87","value_formula":"0","show_in_popup":"0","unit_of_measurement":"","on_tag_value":null,"off_tag_value":null,"on_type":null,"off_type":null,"calibrations":null,"skip_calibration":null}],"services":[{"id":"9","user_id":"1","device_id":"124","name":"Service one","expiration_by":"days","interval":"30","last_service":"2019-08-20","trigger_event_left":"5","renew_after_expiration":"1","expires":"0","expires_date":"2019-09-19","remind":"0","remind_date":"2019-09-14","event_sent":"0","expired":"0","email":"","mobile_phone":""},{"id":"19","user_id":"1","device_id":"124","name":"Ignas","expiration_by":"odometer","interval":"123456","last_service":"0","trigger_event_left":"0","renew_after_expiration":"0","expires":"123456","expires_date":null,"remind":"123456","remind_date":null,"event_sent":"0","expired":"0","email":"","mobile_phone":""},{"id":"20","user_id":"1","device_id":"124","name":"Ignas w","expiration_by":"days","interval":"2000","last_service":null,"trigger_event_left":"0","renew_after_expiration":"0","expires":"0","expires_date":"2023-11-29","remind":"0","remind_date":"1970-01-01","event_sent":"1","expired":"0","email":"","mobile_phone":""},{"id":"21","user_id":"1","device_id":"124","name":"Poiu","expiration_by":"odometer","interval":"12","last_service":"0","trigger_event_left":"0","renew_after_expiration":"0","expires":"12","expires_date":null,"remind":"12","remind_date":null,"event_sent":"0","expired":"0","email":"","mobile_phone":""}],"driver":{"id":"3","user_id":"1","device_id":"128","device_port":null,"name":"Testing","rfid":"ABC451132","phone":"4","email":"","description":"","created_at":"2017-06-20 19:00:43","updated_at":"2018-10-22 14:38:51"},"lastValidLatitude":55.675715,"lastValidLongitude":12.576404,"latest_positions":"55.675698666667/12.5761065;55.675581666667/12.575788833333;55.675362333333/12.575193666667;55.675158166667/12.574806833333;55.675024666667/12.5744815;55.6749245/12.574212666667;55.674809333333/12.5739365;55.674687833333/12.5736325;55.674487333333/12.5731665;55.674489666667/12.572678;55.67469/12.5724915;55.6748885/12.572210333333;55.675289/12.571647333333;55.675530666667/12.5713325;55.6757135/12.571215666667","icon_type":"icon","group_id":71,"user_timezone_id":null,"time":"2019-08-22 13:41:02","course":37,"speed":0}}],"events":[],"time":1566481263,"version":"3.4.3.1"}
Here is just proof-of-concept but I think that you'll get the idea.
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
data class Test(val sensors: List<Sensor>)
data class Sensor(val id: Int, val name: String)
class TestDeserializer : JsonDeserializer<Test> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext): Test {
val jsonObject = json.asJsonObject
val sensorsJson = jsonObject["sensors"]
val sensors = if (sensorsJson.isJsonArray) {
sensorsJson.asJsonArray.map { context.deserialize<Sensor>(it, Sensor::class.java) }
} else {
val typeToken = TypeToken.getParameterized(List::class.java, Sensor::class.java)
val sensorsElement = JsonParser().parse(sensorsJson.asString)
context.deserialize(sensorsElement, typeToken.type)
}
return Test(sensors)
}
}
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Test::class.java, TestDeserializer())
.create()
val jsonWithObjects = """
{
"sensors": [
{
"id": 1,
"name": "test 1"
},
{
"id": 2,
"name": "test 2"
},
{
"id": 3,
"name": "test 3"
}
]
}
""".trimIndent()
val test1 = gson.fromJson(jsonWithObjects, Test::class.java)
val jsonWithStrings = """
{
"sensors": "[{\"id\": 1,\"name\": \"test 1\"},{\"id\": 2,\"name\": \"test 2\"},{\"id\": 3,\"name\": \"test 3\"}]"
}
""".trimIndent()
val test2 = gson.fromJson(jsonWithStrings, Test::class.java)
println(test1 == test2) // prints true
}

Android/Kotlin: GSON doesn't parse correctly some fields JSON

GSON doesn't parse correctly my JSON and returns null for some fields only.
Indeed, I correctly get for every doctor the rpps and code but I have null for lifenId, firstName and lastName.
I have a JSON which is a list of doctor looks like this :
[
{
"lifenId": "238536",
"rpps": "10002732211",
"firstName": "Yolande ",
"lastName": "Couffinhal",
"email": null,
"postalCode": null,
"city": null,
"address": null,
"code": "Médecin Neuro-psychiatrie ",
"phone": null
},
...
]
I'm sure that my JSON is correct because the JSON I receive is exactly the one I post here so these value are not null (for example, I'm not talking about the phone which is null in the JSON)
I tried to parse manually the JSON to get firstName and lastName it works (obviously because I have a good JSON, but I wanted to be sure of that)
There is my data class Doctor :
data class Doctor(var phone: String,
var code: String,
var address: MutableList<String>,
var email: String,
var rpps: String,
var city: String,
var lastName: Any,
var firstName: Any,
var lifenId: String,
var postalCode: String) {
}
My request:
fun getDoctorsByName(name: String, token: String): Single<List<Doctor>> {
val url = "lifen/name/" + name
return repository.getDoctorsByName(url, token).compose(schedulerProvider.getSchedulersForSingle())
}
how I treat the return :
disposable.add(viewModel.getDoctorsByName(search_bar.query.toString(), activity.token)
.doOnError {
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
}
.subscribe(
{ doctors ->
/*val list = ArrayList<Doctor>()
val doc = Doctor("", "","","","",doctors[0]["lastName"] as String,doctors[0]["firstName"] as String,"","") parse manually here
list.add(doc)
adapter.updateList(list)*/
adapter.updateList(doctors)
Toast.makeText(activity, "LIST COMPLETED", Toast.LENGTH_SHORT).show()
}, Throwable::printStackTrace
)
)
You have firstName and lastName as Any in your data class. Change them to String and you should be good. Also if they are sometimes null mark them as nullable and check when accessing them.
I found the bug, when I create the GSON client, I'm putting a filter as follows:
fun provideGson(): Gson {
return GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
}
so I just removed this filter, so that's why on lifenId, firstName and lastName, GSON didn't get these fields.

How to properly make reusable class to catch response data using retrofit?

I am new in Android development, and I am trying to get data from server. the general JSON response structure will be like this
{
"success": "1",
"data": [
{
"customers_id": 4,
"customers_gender": "0",
"customers_firstname": "TES IOS",
"customers_lastname": "TES IOS",
"customers_dob": "2018-12-27",
"email": "TES002#email.com",
"user_name": "TES002",
"customers_default_address_id": 0,
"customers_telephone
},
"message": "Successfully get user data from server"
}
the "success" and "message" field will be the same (will always be string). but the "data" can be different for other request call. It can send user data, store data or product data, or even Array/List of Products.
so I want to make general reusable class to catch that JSON response. the class will be like this, I set the "data" to be Any, and then later it will be casted back to User object:
class ServerData(successStatus: Int, data: Any, message: String) {
val isSuccessfull : Boolean
val data : Any
val message : String
init {
isSuccessfull = successStatus != 0
this.data = data
this.message = message
}
}
the interface is like this:
interface LakuinAPI {
#FormUrlEncoded
#POST("processlogin")
fun performLogin(
#Field("kode_customer") outletCode: String,
#Field("password") password: String
): Call<ServerData>
}
and then I use it in the activity, like the code below:
private fun sendLoginDataToServer(outletCode: String, password: String) {
val call = lakuinAPI.performLogin(outletCode,password)
call.enqueue(object: Callback<ServerData> {
override fun onFailure(call: Call<ServerData>, t: Throwable) {
Toast.makeText(this#LoginActivity,t.localizedMessage,Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<ServerData>, response: Response<ServerData>) {
if (!response.isSuccessful) {
Toast.makeText(this#LoginActivity,"Code: " + response.code(),Toast.LENGTH_LONG).show()
return
}
val lakuinServerData = response.body()
val userList = lakuinServerData?.data as List<User> // the error in here
val userData = userList.first() // the error in here
println(userData)
}
})
}
but I get error message:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap
cannot be cast to com.ssss.lakuinkotlin.Model.User
I give comment in the code above the location of the error. I don't why it happened.
to be honest, I am not if this is the correct way to catch user data from general response JSON like the the JSON above. is there a better way ?
You can use generics to achieve it
class Response<Data> constructor() : ResponseSimple() {
#SerializedName(FIELD_DATA)
var data: Data? = null
private constructor(data: Data) : this() {
this.data = data
}
companion object {
const val FIELD_SUCCESS = "success"
const val FIELD_ERROR = "error"
const val FIELD_DATA = "data"
const val FIELD_MESSAGE = "message"
#JvmStatic
fun <Data> create(data: Data): Response<Data> {
return Response(data)
}
}
}
And ResponseSimple is
open class ResponseSimple {
#SerializedName(Response.FIELD_ERROR)
var error: String = ""
#SerializedName(Response.FIELD_SUCCESS)
var succes: Boolean = false
#SerializedName(Response.FIELD_MESSAGE)
var message:String = ""
}
Then api response should be Call<Response<ServerData>>.
And about ClassCastException, you can't convert ServerData to User just using as.
You need to use Call<Response<ArrayList<User>>> or create class converter.
Try replacing this line :
val userList = lakuinServerData?.data as List<User>
with:
val userList = lakuinServerData?.data as new TypeToken<List<User>>(){}.getType()

Categories

Resources