I'm using Retrofit to call an API which returns a list of objects like this:
[
{
"ID": 1,
"NAME": "text",
"VALUE": 10
},
{
"ID": 2,
"NAME": "text",
"VALUE": 20
},
...
]
My Retrofit API is:
#Headers("content-type: application/json")
#GET("FAMILY_VALUES")
suspend fun getFamilyValues(): List<Family>
Everything is ok so far. Unfortunately sometimes the server sends data with null values:
[
...
{
"ID": 5,
"NAME": "text",
"VALUE": null
},
...
]
I don't know why it happens. The server is out of my control and I cannot change the received data. However this records are useless to my logics and I would like to exclude them from my output List, so that the final list doesn't contain an item with ID=5 at all.
How can I achieve this with Retrofit and GSON? My ugly workaround is to loop the list later and discard the items, I'm looking for a more performing solution that doesn't add the useless items at all.
UPDATE: another solution I've tried was to add a JsonDeserializer that skips the wrong records but it didn't work. My hope was that GSON skipped the null returned items but instead it added null values.
class FamilyDeserializer : JsonDeserializer<Family> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Family? {
json as JsonObject
return if ((json.get("VALUE") !is JsonNull)) {
Family(
json.get("ID").asInt,
json.get("NAME").asString,
json.get("VALUE").asInt
)
} else {
null
}
}
}
Related
I have a json like this. I need to convert it to data class
{
"0": {
"id": "111",
"type": "1",
"items": [
{
"name": "Jack",
"value": "26",
"age": "0.0"
},
{
"name": "Lisa",
"value": "18",
"age": "1.0"
}
]
},
"1": {
"id": "222",
"type": "2",
"items": [
{
"name": "Brown",
"value": "23",
"age": "30.0"
},
{
"name": "Andy",
"value": "18",
"age": "23.0"
}
]
},
"className": "A01"
}
I define the following data class
data class Orders (
val className: String?,
val classes: Map<String, EachClass>
)
data class EachClass (
val id: String,
val type: String,
val items: List<Person>
)
data class Person (
val name: String,
val value: String,
val age: String
)
And the result always show
className=> A01, classes=> null
I searched the stackoverflow and they said using TypeToken. But I have a field called "className" which cannot be convert with EachClass object
val type = object : TypeToken<EachClass>() {}.type
val obj = Gson().fromJson(data, EachClass::class.java)
and I found TypeToken with HashMap<String, Object> is working but its ugly and I need to convert to data class myself.
I'm appreciate if someone can tell me the correct way to convert the json. Thanks!
Gson does not provide built-in functionality for this specific situation so you need to do some manual conversion, but luckily for your use case it is not that much work. The following approach should work:
Parse the JSON as Gson's JsonObject
Remove the className member and store it for later
Parse the JsonObject as Map<String, EachClass>
Construct an Orders instance from the results from step 2 and 3
The complete solution could look like this:
object OrdersDeserializer: JsonDeserializer<Orders> {
private val classesType = object: TypeToken<Map<String, EachClass>>() {}.type
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Orders {
val jsonObject = json.asJsonObject
val className = jsonObject.remove("className").asJsonPrimitive.asString
val classes: Map<String, EachClass> = context.deserialize(jsonObject, classesType)
return Orders(className, classes)
}
}
You would then register it like this:
val gson = GsonBuilder()
.registerTypeAdapter(Orders::class.java, OrdersDeserializer)
.create()
Alternatively you could also convert it to a regular class and use Gson's #JsonAdapter annotation on the Orders class to avoid having to register the deserializer manually.
Note: Normally is recommended to prefer TypeAdapter over JsonSerializer / JsonDeserializer to allow streaming the data for better performance. However, since you need to work on a JsonObject here anyway (therefore non-streaming) using TypeAdapter does not provide an advantage here and might only complicate the implementation a bit.
I'm not sure if polymorphic is the right term to use so my apologies.
I'm working with the following API:
Request body:
{
"user_id": "user_id",
"command": "submit_document",
}
Response:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "documents_rejected", // This is unique for different `data`
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object with known fields and parameters
}
}
I have data classes ready for different types of data responses like:
data class PhoneData(
#SerializedName("phone_number")
val phoneNumber: String? = null,
#SerializedName("phone_status")
val phoneStatus: String? = null
)
for "screen": "phone" and the following for another screen:
data class Data(
val deepLink: String? = null
)
The problem is, at the start, I have to make the following request to retrieve the current screen:
{
"user_id": "user_id",
"command": "get_current_screen",
}
which returns a similar response as above:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "main_screen", // Different types of screen types are known.
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object but the object could contain anything depending on the `screen` type.
}
}
but the data field could contain anything depending on the screen
data class SplashScreenData(
// How do I make this data class combine all other data classes? One ugly approach is to add all the fields from different `data` classes here and use this one only.
)
I found about the RuntimeTypeAdapterFactory for polymorphic cases but am not sure how to make it work when there's no "type" like field within the data object (screen is unique but it's outside the data object).
It would be very helpful if someone has a solution or could point me in a direction.
val frameTextReceived: String = frame.readText()
val jsonObject = JsonParser.parseString(frameTextReceived).asJsonObject
val type = when (jsonObject.get("type").asString) {
TYPE_JOIN_ROOM -> JoinRoom::class.java
TYPE_GAME_MOVE -> GameMove::class.java
TYPE_DISCONNECT_REQUEST -> DisconnectRequest::class.java
else -> BaseModel::class.java
}
val payload = gson.fromJson(frameTextReceived, type)
This is my solution, here I have type parameter by which I can know in which class I have to deserialize the object but in your case you have screen parameter, you can use this.
I have api that return list of workers:
{
"items": [
{
"id": "e0fceffa-cef3-45f7-97c6-6be2e3705927",
"avatarUrl": "https://cdn.fakercloud.com/avatars/marrimo_128.jpg",
"firstName": "Dee",
"lastName": "Reichert",
"userTag": "LK",
"department": "back_office",
"position": "Technician",
"birthday": "2004-08-02",
"phone": "802-623-1785"
},
{
"id": "6712da93-2b1c-4ed3-a169-c69cf74c3291",
"avatarUrl": "https://cdn.fakercloud.com/avatars/alterchuca_128.jpg",
"firstName": "Kenton",
"lastName": "Fritsch",
"userTag": "AG",
"department": "analytics",
"position": "Orchestrator",
"birthday": "1976-06-14",
"phone": "651-313-1140"
},
....
]
}
I want to filter the response so that I only get information about a worker from a specific department.
I tried to do it like this:
interface WorkersApi {
#GET("users")
suspend fun getWorkers(
#Query("department") department: String
): Workers
}
But it return the same list without any filters. How can I filter the response so that I only get information about a worker from a specific department?
*Workers is just data class that hold only one field - list of items(workers)
What you tried to do changes the request to the server. It sends the department as query parameter with the request. If the server doesn't support this parameter nothing happens. I don't know if you work together with the people controlling the backend but you could discuss with them if they could add functionality for filtered results.
If instead, you want to filter the results after getting the full list from the server simply apply a filter on the list that you got.
you could do this on your Workers object
val department = "example"
val filteredList = workersObject.items.filter {it.department == department}
i am using Retrofit and i am getting the response without any problems, i am fetching the data and everything works well, but i only need to get the items with projectType == "FOLDER", discarding the projectType == "PROJECT" ones.
How can i manage this? I also want to sort them based on creation date.
interface ApiInterface {
#GET("projects")
fun getData(#Header("secretKey") apiKey : String): Call<ProjectList>}
Sample output:
{
"projects": [
{
"id": "00001",
"creation": 1611162020,
"projectType": "FOLDER",
"name": "3 - Aufträge",
"archived": false
},
{
"id": "00002",
"creation": 1611158408,
"projectType": "PROJECT",
"name": "Unter",
"archived": false
},
{
"creation": 122234,
"name": "4 - Aus",
"id": "00003",
"projectType": "FOLDER",
"archived": false
}]}
You can easily filter your items using filter function as shown below
pList.projects.filter{ it.projectType == "FOLDER" }
Also if you want to sort your items based on a property, you can use sortedBy and sortedByDescending like below
val ans = pList.projects
.filter{ it.projectType == "FOLDER" }
.sortedBy { it.position.creation }
And for reversed order
val ans = pList.projects
.filter{ it.projectType == "FOLDER" }
.sortedByDescending { it.position.creation }
Because Gson uses reflection to build objects during deserialization via a no-arguments constructor, I am writing a TypeAdapter that will instead use the appropriate constructor for my object. I need this to happen because the object I am deserializing is a subclass of another and needs to call the super constructor appropriately.
I'm trying to deserialize a JSON array that looks something like this (this is example data, not the actual structure of my object):
[
{
"id": "...",
"name": "...",
"description": "...",
"others": [
{
"key1": "val",
"key2": "val"
},
...
]
},
{
"id": "...",
"name": "...",
"description": "...",
"others": [
{
"key1": "val",
"key2": "val"
},
...
]
},
...
]
But I want to use a TypeAdapter during the deserialization of the objects that comprise this array. To be clear, this is how I am initiating the Gson deserialization:
new GsonBuilder()
.registerTypeAdapter(new TypeToken<MyObject>() {}.getType(), new MyTypeAdapter())
.create()
.fromJson(inputJson, MyObject[].class);
And this is MyTypeAdapter:
class MyTypeAdapter : TypeAdapter<MyObject> {
private val type = object : TypeToken<MyObject>() {}.type
override fun write(out: JsonWriter, myObject: MyObject) {
Gson().toJson(myObject, type, out))
}
override fun read(in: JsonReader): MyObject {
Log.d("MyTypeAdapter", "Deserialization via custom TypeAdapter")
val objByReflection = new Gson().fromJson(in, type)
return MyObject(objByReflection.id, objByReflection.name, objByReflection.description, objByReflection.others)
}
}
However, I find that the TypeAdapter is not used for the deserialization; setting a breakpoint doesn't find it being hit, and I get no log output.
Is this happening because I am actually deserializing an array of MyObject? What would be a good way to remedy this?
Thanks!