Android- Json object come as object or array - android

I have this json response from the api, and the response can't be change
data class Weather (
val category: String,
val id: String,
val meta: Meta
)
data class Meta (
val id: String,
val name: String,
val details: String
)
Json respose
{
"weather" : {
"category": "articles",
"id": "1",
"meta": {
"id": "1",
"name": "The shortest article. Ever.",
"details": "see"
},
"weather" : {
"category": "articles",
"id": "2",
"meta": []
}
If meta is empty, it come with an array but if not empty, it come with object.
Retrofit throws
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY
the api can't be modify so this has to be fix on client end. How can I solve this

You can not make this possible
meta object must be an object when have value and null when do not have any value, or an array with value when exist and empty when not exist.
meta can not be an array and object in the same time.
this is very bad mistake from who created this response body.

You can use Any type for meta. and put check at your code level like this.
data class Weather (
val category: String,
val id: String,
val meta: Any
)
if(meta is Meta)
parse it to your Meta object
else
parse it to list

Related

Android - Convert json which has uncertain keys to map using kotlin

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.

How to get field names as an array from json in Kotlin

I am parsing the below sample json using retrofit in android:
{
"success": true,
"timestamp": 1664080564,
"base": "EUR",
"date": "2022-09-25",
"rates": {
"AED": 3.559105,
"AFN": 86.151217,
"ALL": 116.321643,
"AMD": 404.265711
}
}
As you can see there is no array in this json data, but I want the values of rates as a list or a map so that I can get "AED, AFN, ALL, AMD" as an array too. How can i achieve that using retrofit?
You can define rates as a Map<String, Double> in your data class and Retrofit will automatically parse the rates in form of a Map.
data class MyModel(
val success: Boolean,
val timestamp: Long,
val base: String,
val date: String,
val rates: Map<String, Double>
)
so that I can get "AED, AFN, ALL, AMD" as an array too.
For this you can simply use rates.keys to get all the keys.

Need help parsing this type of json in Android using retrofit for API response handling

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.

Retrofit parse JSON into different models

I want to parse JSON like below:
{
"result": 0,
"list": [
{
"id": 58,
"type": "58",
"name": "fooGroup",
"foos": {
"id": "1",
"name": "33",
}
]
}
If I define models like this:
data class Response(val result: Int,
#SerializedName("list") val fooGroup: List<FooGroupResponse>)
data class FooGroupResponse(val id: Int, val type: String, val name: String,
#SerializedName("foos") val fooGroup: List<Foo>?)
data class Foo(val id: Int, val name: String)
then everything works fine.
Right now I want to take these out as a model:
"id": 58,
"type": "58",
"name": "fooGroup",
That is add one more model FooGroup like below:
data class Response(val result: Int,
#SerializedName("list") val fooGroup: List<FooGroupResponse>)
data class FooGroupResponse(val fooGroup: FoolGroup,
#SerializedName("foos") val fooGroup: List<Foo>?)
data class Foo(val id: Int, val name: String)
data class FooGroup(val id: Int, val type: String, val name: String)
But there's no #SerializedName can be set for FooGroup, is it possible?
Thanks.
I think you will have to restructure your JSON. The parser is going to make a faithful representation of the JSON string as a Java object. Your first example is the faithful representation. What you want to do is not faithful to the JSON received.
{
"result": 0,
"list": [
{
"fooGroup": {
"id": 58,
"type": "58",
"name": "fooGroup"
},
"foos": [
{
"id": "1",
"name": "33"
}
]
}
]
}
BUT you can do it manually where you parse the object yourself. Here is a how to with GSON but it should be easily convertible if you prefer another lib.
https://www.woolha.com/tutorials/retrofit-2-define-custom-gson-converter-factory
Simply pull "id", "type", "name" from the JSON as its deserializing and make a composite data class.
data class FooGroup(val id: Int, val type: String, val name: String)
//You deserialize you JSON into a List of Foos
data class Foos(val foo: Foo, val fooGroup : FooGroup)

Can't convert json response using gson

When I start request using retrofit 2.0, I'm getting this error com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
and don't know why, becoause I have a valid json.
I have also seen this kind of error but didn't helped, I don't want to use JsonReader and this kind of stuff
P.S I'm using Gson for converter
[
{
"fullname": "name",
"contact": "blah",
"uid": "001"
},
{
"fullname": "name2",
"contact": "blah2",
"uid": "002"
}
]
this is my call interface method
#FormUrlEncoded
#POST("url")
fun startRequest(#Field("someField") someField: String) : Call<List<MyModelExample>>
and this is my response MyModelExample class
open class MyModelExample : RealmObject() {
#SerializedName("fullname")
var fullName: String? = null
#SerializedName("contact")
var contact: String? = null
#PrimaryKey
#SerializedName("uid")
var uid: String = ""
}

Categories

Resources