I have web service that returns json response like this
"map": [
[
0,
"A mother of our mother"
],
[
2,
"A brother of our father"
],
[
1,
"A daughter of our sister"
]
],
How do i define data class to handle this response?
data class Map(
#SerializedName("map")
val map : <What type/class definition here>
)
This JSON represent array of objects array.
data class Map(
#SerializedName("map")
val map : List<List<Any?>>
)
That would be something like that:
data class Map(
#SerializedName("map")
val map : List<Collection<Any>>
)
I've been able to parse it with this test:
#Test
fun testJson() {
val myMap = Gson().fromJson(json, Map::class.java)
myMap.map.forEach { it ->
println(it)
}
assert(myMap != null)
}
Remember to wrap your json with {} for the test
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.
Please show me a way to make a proper model for this JSON so that I get "Indian Premier League" as the key and the array next to it as value. We can have multiple leagues as well in the json.
{
"keySeriesNews": {
"Indian Premier League": [
{
"id": 203,
"slug": "tata-ipl-2022-why-delhi-capitals-bought-shardul-thakur-for-inr-1075-crore",
"competition_id": 3269,
"image_id": 1203,
"image_caption": "Shardul Thakur in action",
"start_date": "2022-03-05 17:25:38",
"created_at": "2022-03-05 12:08:19",
"updated_at": "2022-04-15 06:50:30",
"headline": "TATA IPL 2022: Why Delhi Capitals bought Shardul Thakur for INR 10.75 crore",
"sport_id": 15,
"image": {
"id": 1203,
"file_name": "shardulthakur_new.webp",
"created_at": "2022-04-15 06:47:41",
"image_path": "https://stagingkisma.6lgx.com/storage/images/shardulthakur_new_320x320.webp"
},
"competition": {
"id": 3269,
"slug": "indian-premier-league-2",
"competition_name": "Indian Premier League"
}
}
]
}
}
I have used this model to parse in retrofit but it is not fetching any data from the API. It is completely blank. No data in it.
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: JSONObject? = JSONObject()
)
However, when I use this model, it fetches data and I can access it. But problem is that it is hardcoded. I mean, these models will not capture data if the league name changes in any case. Here are the models which captured data.
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: KeySeriesNews? = KeySeriesNews()
)
data class KeySeriesNews (
#SerializedName("Indian Premier League" ) var league : ArrayList<League> = arrayListOf()
)
data class League (
#SerializedName("id" ) var id : Int? = null,
#SerializedName("slug" ) var slug : String? = null,
#SerializedName("competition_id" ) var competitionId : Int? = null,
#SerializedName("image_id" ) var imageId : Int? = null,
#SerializedName("image_caption" ) var imageCaption : String? = null,
#SerializedName("start_date" ) var startDate : String? = null,
#SerializedName("created_at" ) var createdAt : String? = null,
#SerializedName("updated_at" ) var updatedAt : String? = null,
#SerializedName("headline" ) var headline : String? = null,
#SerializedName("sport_id" ) var sportId : Int? = null,
#SerializedName("image" ) var image : Image? = Image(),
#SerializedName("competition" ) var competition : Competition? = Competition()
)
I have coded for a parser on the generic side to handle key-value type JSON like this but the JSON object was empty when I used the first approach of the data model. I need to make a generic parser to fetch league names as well as their data in key-value format since there can be multiple leagues that can come in this response as well.
PS: This is my parser which is getting empty JSON Object
private fun parseJSONData(data: JSONObject){
try {
val jsonObject = JSONObject(data)
for (key in jsonObject.keys()) {
Toast.makeText(
this#SeriesFragment.requireContext(),
"Key : " + key + " Value: " + jsonObject.optString(key),
Toast.LENGTH_SHORT
).show()
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
Your help is much appreciated. Thanks.
Just a tip - if you already have the JSON available, you can use this plugin to easily generate a first draft of your model and adapt it if needed.
Some questions:
If you can have multiple leagues in your response, shouldn't keySeriesNews also be a list and not just a JSON object? For example like this:
{
"keySeriesNews": [
{
"id": 203,
"title": "Indian Premier League",
"slug": "tata-ipl-2022-why-delhi-capitals-bought-shardul-thakur-for-inr-1075-crore",
"competition_id": 3269,
"image_id": 1203,
...
}
]
}
What's your reasoning for handling JSON manually instead of using a ConverterFactory?
Where and how are you calling parseJsonData?
Well, I am not sure about this is correct or not. If anyone has a standard way of doing it, it is much appreciated. However, I have used the JSONElement instead of JSONObject or JSONArray and have used Map to handle key-value type data in my model, and GSONConvertorFactory has got this one right and fetched data correctly. This is the model I used:
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: HashMap<String, JsonElement>? = HashMap()
)
And I will parse JSONElement in my parseJsonData function to handle the key-value of this nonstandard JSON coming from API.
Hope this helped you in some way.
I'm in the process of creating a table section in Jetpack Compose that deserializes the data from a JSON.
At this point, I'm simply trying to display the data (the table formatting and clean up will come later, so you can ignore that part). Things have been going okay until I got to a part where the table row values are an array of arrays. I'm getting this error:
Error:
Expected class kotlinx.serialization.json.JsonObject as the serialized body of ...TableRow, but had class kotlinx.serialization.json.JsonArray
Here is the part of the test JSON that is being parsed for this table:
{
"title": "TRANSACTION HISTORY",
"type": "Table",
"names": [
"Date",
"Amount",
"Lender",
"Borrower"
],
"values": [
[
"07\/01\/2014",
"$315,000",
"Steve's Lending Co.",
"Glenn Danzig"
],
[
"10\/13\/2011",
"$236,000",
"Feet Company",
"Marcus Toeswater"
]
]
},
{
"title": "ASSESSMENT & TAX",
"type": "Table",
"names": [
"Year",
"Property Taxes",
"Tax Assessment"
],
"values": [
[
"2017",
"$6,068",
"$395,000"
],
[
"2016",
"$5,864",
"$372,000"
],
[
"2015",
"$5,609",
"$341,500"
]
]
},
Here's the code I have right now. I'm simply trying to post two rows of data:
1 with the column header names, and
1 with all of the row data (doesn't have to look nice at this point - I'm just trying to get the mapping done)
#Serializable
#SerialName("Table")
#Parcelize
data class TableSection(
override val title: String? = null,
#SerialName("names")
val columnHeader: ArrayList<String?> = arrayListOf(),
#SerialName("values")
val tableData: ArrayList<TableRow> = arrayListOf(),
) : Section() {
#Composable
override fun Content(modifier: Modifier) {
return Column {
Row {
columnHeader.map {
Text(it ?: "")
}
}
Row {
tableData.map { row ->
row.tableRowValues.map { value ->
Text(value ?: "")
}
}
}
}
}
}
#Serializable
#Parcelize
data class TableRow(
#Serializable
val tableRowValues: ArrayList<String?> = arrayListOf(),
) : Parcelable
Note: The Title and the Table Column Headers work just fine. It's the table data that is throwing the error.
I ended up creating a custom serializer to get the nested values:
#Serializable(with = TableRowSerializer::class)
#Parcelize
data class TableRow(
val tableRowValues: List<String?> = arrayListOf(),
) : Parcelable
object TableRowSerializer : KSerializer<TableRow> {
private val serializer = ListSerializer(String.serializer())
override val descriptor: SerialDescriptor = serializer.descriptor
override fun serialize(encoder: Encoder, value: TableRow) {
val rowValues = value.tableRowValues
rowValues.let{rowValues as? List<String>}
?.let { encoder.encodeSerializableValue(serializer, it) }
}
override fun deserialize(decoder: Decoder): TableRow {
return TableRow(decoder.decodeSerializableValue(serializer))
}
}
I have this response, i have problem when i want to convert to pojo.
"equity": {
"0": {
"name": [
"Abc"
],
"code": [
"3410"
],
"ending_balance": [
301834470
]
},
"1": {
"name": [
"Xyz"
],
"code": [
"2180"
],
"ending_balance": [
0
]
},
"2": {
"name": [
"Pqr"
],
"code": [
"9220"
],
"ending_balance": [
0
]
},
"total_equity": 301834470
}
}
I'm confused about giving the right data type, because there are arrays("0","1","2") that contain objects and "total_equity" that contain number.
I've tried to give the map data type, but it will be error for "total_equity"
var equity: Map<String, EquityDto?>
If you know the solution for this problem please help me. Thank you
You can use
var equity: Map<String, Any>
While accessing the Any Data type u can compare the varrriable type(instance of) and use the value as following
if (valueofMap is Int){
//here is integer value
}
if (valueofMap is yourDataClass){
//your custom class
}
There can be a solution to this, but it will be a lengthy one.
One solution can be to transform the response to Map<String, Any> then you will have to check the type every time you have to use it and it can really annoying when you are using it in multiple classes.
Another solution can be to create a custom Custom Type Adapter which you can pass to the Retrofit Instance in the addConverterFactory method.
To create a custom adapter, you just have to follow the following steps:
Create a model in which you want to store the data. In your case it can be :
data class ApiResponse(
val data: Map<String, EquityDto?>,
val totalEquity:Int
)
Create the Adapter:
class StudentAdapter : TypeAdapter<ApiResponse?>() {
#Throws(IOException::class)
fun read(reader: JsonReader): ApiResponse {
val student:ApiResponse? = null
reader.beginObject()
var fieldname: String? = null
while (reader.hasNext()) {
var token = reader.peek()
if (token == JsonToken.NAME) {
//get the current token
fieldname = reader.nextName()
}
if ("total_equity" == fieldname) {
token = reader.peek()
student?.totalEquity = reader.nextInt()
}else {
token = reader.peek()
student?.data?.set(fieldname.toString(), reader.nextString())
}
}
reader.endObject()
return student
}
#Throws(IOException::class)
fun write(writer: JsonWriter, student: ApiResponse) {
writer.beginObject()
writer.name("data")
writer.value(student.data.toString())
writer.name("totalEquity")
writer.value(student.totalEquity)
writer.endObject()
}
}
If you know a better way to create a type adapter then you can surely use that.
I'm pretty new to Kotlin so here I go.
I have some JSON data that sometimes returns an object or it can return an array. See example:
Object:
"planograms": {
"planogram": {
"multiple-facings": 1,
"planogram-seq": 838,
"planogram-id": 252871,
"planogram-desc": "Bulk Hardware - No Springs"
}
},
Array:
"planograms": {
"planogram": [
{
"multiple-facings": 1,
"planogram-seq": 112,
"planogram-id": 262027,
"planogram-desc": "Cat Food - Cat Dry"
},
{
"multiple-facings": 1,
"planogram-seq": 94,
"planogram-id": 276508,
"planogram-desc": "Cat Food - Cat Dry - A"
}
]
},
I am using Retrofit2 for the parsing so here is the code that does that:
fun getItemInfoExtended(#Body itemId: ItemData): Call<DefaultResponse>
DefaultResponse has more data that includes the object/array.
My question is how can I get to return the object or array. currently, I have a list in my data class, so when the object is returned I get an error stating the issue of not an array:
data class Planograms (
val planogram: List<Planogram>?
)
data class Planogram (
val multipleFacings: Long,
val planogramSeq: Long,
val planogramID: Long,
val planogramDesc: String
)
Thanks in advance for your input.