I've read a few posts on here saying to use gson or something else.
Android Kotlin parsing nested JSON
I am planning on switching to gson later but wondering how to parse with kotlin.
JSON object:
{
"childScoresList": [
{
"child_id": "1",
"score_date": "2022-03-27",
"category_id": "1",
"category_name": "Preschool Math",
"classes": [
{
"category_name": "Preschool Math",
"class_name": "Number Blocks",
"class_id": "1",
"class_description": "Ones, Tens, Hundreds Blocks used to create given numbers.",
"skills": [
{
"skill_id": "1",
"skill_name": "Place Value",
"skill_description": "Knowing the place value of specific elements.",
"skill_score": "50"
}
]
}
]
}
]
}
Kotlin code:
val obj = JSONObject(response.toString())
val jsonArray = obj.getJSONArray("childScoresList")
for (i in 0 until jsonArray.length()) {
val categoryName = jsonArray.getJSONObject(i).getString("category_name")
}
How do I get data in class_name?
I tried various things but could not get it to work.
You must first get JSONArray from the object according to the following code and then access the class_name variable
val obj = JSONObject(js)
val jsonArray = obj.getJSONArray("childScoresList")
for (i in 0 until jsonArray.length()) {
val classes = jsonArray.getJSONObject(i).getJSONArray("classes")
for (x in 0 until classes.length()) {
val categoryName = classes.getJSONObject(x).getString("category_name")
val className = classes.getJSONObject(x).getString("class_name")
}
}
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 implemented a function to validate a given JSONObject against a given JSON Schema JSONObject.
Here is the code:
class JsonSchemaUtilsTest {
//a JSON as a String
val testJsonStr : String =
"""
{
"duration": 28298080890890809809
}
""".trimIndent()
//The JSON schema as a String
val testJsonSchemaStr : String =
"""
{
"${'$'}schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"duration": {
"type": "number",
}
},
"required": [
"duration"
]
}
""".trimIndent()
#Test
public fun test() {
val json : JSONObject = JSONObject(testJsonStr)
val schemaJson : JSONObject = JSONObject(testJsonSchemaStr)
var boolean : Boolean = JsonSchemaUtils.validate(json, schemaJson)
//The following line fails with "org.everit.json.schema.ValidationException: #/duration: expected type: Number, found: String"
Assert.assertTrue("json validates against schemaJson", boolean)
}
}
Im using the https://github.com/everit-org/json-schema library.
So somehow the given Number "duration": 28298080890890809809 is converted to a String when using the JSONObject(String) constructor.
Thanks in Advance
When I use retrofit, I get JsonSyntaxException : Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3 path $[0] How can I parse it?
[
[
{
"resturan_name": "هتل شاه عباس",
"menu_name": "کباب سلطانی",
"food_name": "پیش غذا"
},
{
"resturan_name": "هتل شاه عباس",
"menu_name": "کباب سلطانی",
"food_name": "پیش غذا"
}
],
[
{
"resturan_name": "هتل شاه عباس",
"menu_name": "کباب سلطانی",
"food_name": "عصرانه"
},
{
"resturan_name": "هتل شاه عباس",
"menu_name": "کباب سلطانی",
"food_name": "عصرانه"
}
]
]
You have an array of array of objects. So, when you're parsing your JSON, you have to use JSONArray:
val jsonArray = JSONArray(your_json)
The json received has a list but you maybe use json object pars in rerofit
see this link for resolve
Parse JSON array response using Retrofit & Gson
Change
Call<...> getListOf....(...);
To
Call<List<...>> getListOf....(...);
Using Response Model
Make your Response Model Class like this Using Gson,
class ResponseModel : ArrayList<ResponseModel.ResponseModelSubList>(){
class ResponseModelSubList : ArrayList<ResponseModelSubList.ResponseModelSubListItem>(){
#Parcelize
data class ResponseModelSubListItem(
#SerializedName("food_name")
val foodName: String? = "",
#SerializedName("menu_name")
val menuName: String? = "",
#SerializedName("resturan_name")
val resturanName: String? = ""
) : Parcelable
}
}
Parse JSON like this,
val response = ResponseModel() // Here response is getting from retofit or other networking lib. you use.
for (i in 0 until response.size) {
val responseList = response[i]
for (j in 0 until responseList.size) {
var foodName = responseList[j].foodName
var menuName = responseList[j].menuName
var restaurantName = responseList[j].resturanName
}
}
Using Manually Parsing
val jsonArray = JSONArray(response)
for (i in 0 until jsonArray.length()){
val jsonArray1 = jsonArray.get(i) as JSONArray
for (j in 0 until jsonArray1.length()){
var jsonObj = jsonArray1.get(j) as JSONObject
var foodName = jsonObj.getString("food_name")
var menuName = jsonObj.getString("menu_name")
var restaurantName = jsonObj.getString("resturan_name")
}
}
So I made an api in laravel and it returns a response like this:
{
"message": "The given data was invalid.",
"errors": {
"email": [
"The email has already been taken."
],
"mobile": [
"The mobile has already been taken."
]
}
}
Can somebody show me how to get the specific values from errors?
You may create model representing your error json and use Gson to parse it. Here is some short example.
data class Errors(
val email: List<String>,
val phone: List<String>
)
data class YourErrorModel(
val message: String,
val errors: Errors
)
fun parseError(response: Response<*>): YourErrorModel? {
val errorBody = response.errorBody()?.string() ?: return null //No error body present
return Gson().fromJson(errorBody, YourErrorModel::class.java)
}
Also don't forget to handle nullable types in your response. And i suggest you to return just string, not array if that is exact error for field.
How about this :
JSONObject errorObject = yourJSONObject.optJSONObject("errors");
if (errorObject != null){
JSONArray emailMsgArray = errorObject.getJSONArray("email");
JSONArray mobileMsgArray = errorObject.getJSONArray("mobile");
String emailMsg= emailMsgArray.getString(0);
String mobileMsg= mobileMsgArray .getString(0);
}