parse/serialize complex json object with GSON - android

This json object can be very very large sometimes, I would like to parse it with GSON but I do not quite get how the format of my java objects should be to parse them.
What would really help me are some very contextual examples, given this object, how would I form my java model objects to hold the data in a gson.fromJSON method? My current objects get filled with "null"
I explain the simplicity of the object at the bottom
{
response: {
user_is_following: 0,
name: "Tennesee",
submitted_requests: 429,
completed_requests: 34,
request_types: {
c_id: 1064,
request_types: [
{
objectKey: {
id: 15699,
name: "Complaint",
has_custom_fields: 0,
disable_title: 0,
disable_description: 0,
force_private: 0,
image: null
}
},
{
objectKey: {
id: 15700,
name: "Compliment",
has_custom_fields: 0,
category_id: 605,
disable_title: 0,
disable_description: 0,
force_private: 0,
image: null
}
},
{
objectKey: {
id: 17574,
name: "Custom Fields, all of them",
has_custom_fields: 1,
disable_title: 0,
disable_description: 0,
force_private: 0,
image: null,
custom_fields: [
{
custom_field: {
id: "1663",
name: "I'm a text input",
description: "I'm a text input description",
type: "text",
required: 1,
is_public: 1,
options: [
]
}
},
{
custom_field: {
id: "1664",
name: "I'm a text input display only",
description: "I'm a text input display only description",
type: "display",
required: 0,
is_public: 0,
options: [
]
}
},
{
custom_field: {
id: "1665",
name: "I'm a checkbox",
description: "I'm a checkbox description",
type: "checkbox",
required: 0,
is_public: 1,
options: [
]
}
},
{
custom_field: {
id: "1666",
name: "I'm a single select",
description: "I'm a single select description",
type: "singleselect",
required: 1,
is_public: 0,
options: [
{
option: {
id: "3751",
name: "A 123 !###",
description: "A 123 !### description"
}
},
{
option: {
id: "3752",
name: "B ",
description: "B description"
}
},
{
option: {
id: "3753",
name: "C",
description: "C description"
}
},
{
option: {
id: "3754",
name: " D",
description: "D description"
}
}
]
}
},
}
],
s_types: [
],
categories: [
{
category: {
id: 618,
client: 1064,
name: "Abc",
gov_creator: 1841,
description: "",
parent: 607,
date_created: 1368137256,
image: null
}
},
{
category: {
id: 602,
client: 1064,
name: "Animal Control",
gov_creator: 2275,
description: "",
parent: null,
date_created: 1367878768,
image: null
}
},
}
],
assets: [
],
benchmark: 0.36078095436096
},
status: {
type: "success",
message: "Success",
code: 200,
code_message: "Ok"
}
}
}
The real meat is in the request_types key, the second one, which is a JSONArray. Each index contains an object, each object can contain a Custom Fields key which is a json array as well, which in some cases can contain an options json array.
I have models for all of these, for a different parsing paradigm, but not for GSON. I will need to use GSON now because of memory limitations

You will need lets say class A with one instance of class B in it.
public class A {
private B response;
}
then you will need class B to have 1 instance of class C
public class B {
private C request_types;
}
Then C would contains int c_id and array of a class D and a class for each of the other arrays as well. Then class D would contain a single object for class E called objectKey. Class E would contains all the fields under objectKey...
So on and so forth... you're right the JSON is crazy convoluted.

Related

JSON Serialization / Deserialization Kotlin Android Jetpack Compose Table

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))
}
}

Need to parse dynamic HashMap<String, HashMap<String, HashMap<String,String>>>

I am parsing a json and than i need to populate the values on the graph, the response which i am getting as below
{
"message": "Successful.",
"data": {
"stats": {
"Total Transactions": 1,
"Today Transactions": 0,
"Today Pending Transactions": 0,
"Payment links created": 2,
"Api payments": 0
},
"otherData": {
"countiesData": {
"2021": {
"US": 1
}
},
"transactionTypesData": {
"2021": {
"PAYMENT_LINK": 1
}
},
"graphData": {
"2021": {
"SUCCESS": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"7": 0,
"8": 0,
"9": 1,
"10": 0,
"11": 0,
"12": 0
}
}
},
"currentMoth": 3200,
"preMonthAvr": 3200
}
},
"code": 200,
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
}
I have created the model data for graphData within otherData.
data class OtherData(
var countiesData : Any,
var transactionTypesData: Any,
var graphData : HashMap<String,HashMap<String,HashMap<String,String>>>
)
i got the year with in graphData by these lines but i am unable to get other values within year object.
it.data.data.otherData.graphData.let { graphYear->
if (graphYear.isNotEmpty()){
tvYearForRevenueReport.text = graphYear.keys.elementAt(0)
tvFirstGraphLabel.text = graphYear.getValue(graphYear.keys.elementAt(0)) // giving error at this line
}
}
so my question is what i am doing it is the best way to do it? if it is than how, and if there is any work around will be glad to hear.
Using Gson I was able to parse your JSON:
Creating a Data class:
data class Data(
val data: InnerData
)
And an InnerData class:
data class InnerData(
val otherData: OtherData
)
And using your OtherData class:
data class OtherData(
var countiesData : Any,
var transactionTypesData: Any,
var graphData : HashMap<String,HashMap<String,HashMap<String,String>>>
)
Calling Gson to parse the JSON input:
val parsed = Gson().fromJson(json, Data::class.java)
I've used your JSON as an input and everything was working as expected.

How to generate request model having same object which having multiple types

How to generate request model having same object which having multiple types:
{
"questionnaire": 2,
"response": [
{
"answer": {
"id": 8,
"option_data": {
"description": "",
"text": "As much as i ever did",
"value": "4"
}
},
"question_id": 4
},
{
"answer": {
"option_data1": [
{
"text": "",
"value": 2
}
]
},
"question_id": 2
}
]
}
There is a plugin AS Json to DataClass convertor. You can find here
data class s(
val questionnaire: Int?, // 2
val response: List<Response>? )
data class Response(
val answer: Answer?,
#Json(name = "question_id")
val questionId: Int? // 4 )
data class OptionData1(
val text: String?,
val value: Int? // 2 )
data class OptionData(
val description: String?,
val text: String?, // As much as i ever did
val value: String? // 4 )
data class Answer(
val id: Int?, // 8
#Json(name = "option_data")
val optionData: OptionData?,
#Json(name = "option_data1")
val optionData1: List<OptionData1>? )
As you see there is ? in some fields. You can use it

How would I go about de-serializing a list of objects using Kotlin's serialization library?

I've been running into the following exception at runtime with the debugger trying to de-serialize data from my Algolia index for my Kotlin Android recipe app I am trying to create by using the Kotlinx.Serialization library. The app compiles and runs fine, but no results show on the UI.
kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset -1: Failed to parse 'int'.
JSON input: {"amount":1.5,"name":"green beans","original":"1.5 pounds of green beans","unit":"pounds","unitLong":"pounds","unitShort":"lbs"}
Now from the looks this exception, It looks like the de-serializer is getting confused try to de-serialize my Ingredients data class. How would I go about de-serializing it?.
Example JSON data that is being sent over.
{
"cuisine": "European",
"diet": "Vegetarian",
"difficulty": 2,
"dishType": "Dinner",
"duration": 30,
"durationUnit": "minutes",
"image": "https://c.recipeland.com/images/r/1396/12f9fc271d8f1bfac5f6_550.jpg",
"ingredients": [
{
"amount": 1.5,
"name": "green beans",
"original": "1.5 pounds of green beans",
"unit": "pounds",
"unitLong": "pounds",
"unitShort": "lbs"
},
{
"amount": 1,
"name": "onion",
"original": "1.5 medium onion",
"unit": "medium",
"unitLong": "medium",
"unitShort": "med"
},
{
"amount": 2,
"name": "garlic",
"original": "2 teaspoons of garlic",
"unit": "teaspoons",
"unitLong": "teaspoons",
"unitShort": "tsps"
},
{
"amount": 1,
"name": "olive oil",
"original": "1 teaspoon olive oil",
"unit": "teaspoon",
"unitLong": "teaspoon",
"unitShort": "tsps"
},
{
"amount": 1,
"name": "mushrooms",
"original": "1 cup mushrooms",
"unit": "cup",
"unitLong": "cup",
"unitShort": "cup"
},
{
"amount": 1,
"name": "cherry tomatoes",
"original": "1 cup cherry tomatoes",
"unit": "cup",
"unitLong": "cup",
"unitShort": "cup"
}
],
"name": "Green Beans with Mushrooms and Cherry Tomatoes",
"preparation": [
"Steam green beans until tender.",
"Drain and set aside. Sauté onion and garlic in a medium skillet coated with olive oil, until tender. About 2 to 3 minutes.",
"Add mushrooms and sauté until tender. Stir in green beans and tomotoes until heated."
],
"yield": 4,
"objectID": "0"
}
I have my data classes for a recipe set up as the following:
Recipe.kt
#IgnoreExtraProperties
#Serializable
data class Recipe(
var difficulty: Int = 0,
var dishType: String? = null,
var duration: Int = 0,
var durationUnit: String? = null,
var image: String? = null,
var diet: String? = null,
var cuisine: String? = null,
var name: String? = null,
var ingredients: List<Ingredient> = emptyList(),
var preparation: List<String> = emptyList(),
var yield: Int = 0
) {
Ingredient.kt
#Serializable
data class Ingredient(
var amount: Int = 0,
var name: String? = null,
var original: String? = null, // Original text of the ingredient
var unit: String? = null,
var unitLong: String? = null,
var unitShort: String? = null
)
This block of code I got from Algolia's getting started guide for InstantSearch Android that de-serializes the data from the index.
private val datasourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
hit.deserialize(Recipe.serializer()) // Problem line I assume
}
val pagedListConfig = PagedList.Config.Builder().setPageSize(50).build()
val recipes: LiveData<PagedList<Recipe>> =
LivePagedListBuilder(datasourceFactory, pagedListConfig).build()
val searchBox =
SearchBoxConnectorPagedList(searcher, listOf(recipes))
I've tried to manually create the object by using the following code, but I run into issues when trying to create the list of ingredients.
val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
Recipe(
hit.json.getPrimitive("difficulty").content.toInt(),
hit.json.getPrimitive("dishType").content,
hit.json.getPrimitive("duration").content.toInt(),
hit.json.getPrimitive("durationUnit").content,
hit.json.getPrimitive("image").content,
hit.json.getPrimitive("diet").content,
hit.json.getPrimitive("cuisine").content,
hit.json.getPrimitive("name").content,
listOf(
Ingredient(
hit.json.getPrimitive("amount").content.toInt(),
hit.json.getPrimitive("name").content,
hit.json.getPrimitive("original").content,
hit.json.getPrimitive("unit").content,
hit.json.getPrimitive("unitLong").content,
hit.json.getPrimitive("unitShort").content
)
),
hit.json.getArray("preparation").content.map { prep -> prep.content },
hit.json.getPrimitive("yield").content.toInt()
)
}
I'm not 100% sure if I'm properly creating the preparation property member correctly as well as the whole creating the list of ingredients has side-tracked me. Any help would be greatly be appreciated and I apologize for my first post on here being a long one. I've been going at this for a couple of days already and I'm stumped as to what to do next.
As you can see this line:
kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset -1: Failed to parse 'int'.
Here JsonDecodingException exception occur that's why it is not giving proper response. You must check your all data classes are same variable which is in JSON Object.
Here I found 1 issue in your data class, First check this JSON Reposne:
"amount": 1.5
and now check your data class, which has var amount: Int = 0
#Serializable
data class Ingredient(
var amount: Int = 0,
var name: String? = null,
var original: String? = null, // Original text of the ingredient
var unit: String? = null,
var unitLong: String? = null,
var unitShort: String? = null
)
Here JSON Object is in Float and you are storing in it Int, which may cause exception. Make sure all values in data class are proper.
Or for work around you just make String all variable in data class to check all response show proper, than after just convert them to Int, Float according to your requirements.

Android JSON Nested Arrays

I've tried to imitate the chosen answers in this sort of problem but I'm unsure why I'm failing to retrieving the URLs of the "attachments" in this.
What I'm after is a way to get every "post" and then grab every "attachment" image URL to save as a string. I've tried doing this on my emulator but it just stalls and runs forever. For certain reasons I am unable to use my real phone as a debugger too or else I would post a logcat.
One thing I am certain is that everything, minus the attachments, is coming in correctly. I've managed to get the posts downloading but cannot get anything thats nested. I'm newer to JSON so any help is very appreciated.
My Async:
// you can make this class as another java file so it will be separated from your main activity.
// https://www.codeofaninja.com/2013/11/android-json-parsing-tutorial.html
public class AsyncTaskParseJson extends AsyncTask<String, String, String> {
private ArrayList<RssFeedItem> tempArray = new ArrayList<RssFeedItem>();
final String TAG = "AsyncTaskParseJson";
private ProgressDialog progress;
// set your json string url here
String yourJsonStringUrl = "http://www.prindlepost.org/?json=tag_slug=java";
// contacts JSONArray
JSONArray dataJsonArr = null;
JSONArray imageURLArr = null;
#Override
protected void onPreExecute() {
progress = new ProgressDialog(getActivity());
progress.setTitle("Downloading Prindle's Posts");
progress.setMessage("This should just take a moment.");
progress.show();
}
#Override
protected String doInBackground(String... arg0)
{
try
{
// instantiate our json parser
JsonParser jParser = new JsonParser();
// get json string from url
JSONObject json = jParser.getJSONFromUrl(yourJsonStringUrl);
// get the array of users
dataJsonArr = json.getJSONArray("posts");
// loop through all users
for (int i = 0; i < dataJsonArr.length(); i++)
{
JSONObject c = dataJsonArr.getJSONObject(i);
// Storing each json item in variable
String id = c.getString("id");
String type = c.getString("type");
String slug = c.getString("slug");
String title = c.getString("title");
String content = c.getString("content");
String author = c.getString("author");
//http://stackoverflow.com/questions/19748829/android-get-json-array-nested-in-array
JSONObject attachments = c.getJSONObject("attachments");
Log.d("attachment",""+attachments.getString("url"));
// show the values in our logcat
Log.e(TAG, "id: " + id
+ ", type: " + type
+ ", slug: " + slug
+ ", title: " + title
+ ", author: " + author
+ ", content: " + content + "\n\n");
tempArray.add(new RssFeedItem(title, content, "", 0, new Date(), author));
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
The JSON: http://www.prindlepost.org/?json=tag_slug=java
{
status: "ok",
count: 10,
count_total: 334,
pages: 34,
posts: [
{
id: 4230,
type: "post",
slug: "crowdsourcing-justice",
url: "http://www.prindlepost.org/2015/06/crowdsourcing-justice/",
status: "publish",
title: "Crowdsourcing Justice",
title_plain: "Crowdsourcing Justice",
content: "<p>The video begins abruptly. Likely recorded on a phone, the footage is shaky and blurry, yet the subject is sickeningly unmistakeable: a crying infant being repeatedly and violently dunked into a bucket of water. First it is held by the arms, then upside down by one leg, then grasped by the face as an unidentified woman pulls it through the water. Near the end of the video, the infant falls silent, the only remaining audio the splashing of water and murmured conversation as the child is dunked again and again.</p> <div class="more-link-wrap wpb_button"> Read more</div> ",
excerpt: "<p>Facebook’s decision not to censor a video of child abuse poses questions of censorship, activism and online justice. </p> ",
date: "2015-06-09 14:00:19",
modified: "2015-06-10 09:53:36",
categories: [
{
id: 433,
slug: "crime-and-law",
title: "Crime and Law",
description: "",
parent: 63,
post_count: 14
},
{
id: 38,
slug: "ethics-news",
title: "Ethics News",
description: "",
parent: 0,
post_count: 153
},
{
id: 63,
slug: "society-ethics-news",
title: "Society",
description: "",
parent: 38,
post_count: 187
}
],
tags: [
{
id: 180,
slug: "abuse",
title: "abuse",
description: "",
post_count: 2
},
{
id: 481,
slug: "child-abuse",
title: "child abuse",
description: "",
post_count: 1
},
{
id: 482,
slug: "doxxing",
title: "doxxing",
description: "",
post_count: 1
},
{
id: 57,
slug: "facebook",
title: "Facebook",
description: "",
post_count: 4
},
{
id: 470,
slug: "internet",
title: "internet",
description: "",
post_count: 2
},
{
id: 130,
slug: "justice",
title: "justice",
description: "",
post_count: 2
},
{
id: 59,
slug: "social-media",
title: "social media",
description: "",
post_count: 4
}
],
author: {
id: 43,
slug: "connergordon_2016",
name: "Conner Gordon",
first_name: "Conner",
last_name: "Gordon",
nickname: "connergordon_2016",
url: "http://connergordon.tumblr.com",
description: "Conner is a web and social media intern at the Prindle Institute. A Political Science and English double major from Carmel, Indiana, Conner's ethical interests lie in memory studies, conflict analysis and the ethics of representation. He also has interests in literature, art and photography."
},
comments: [ ],
attachments: [
{
id: 4233,
url: "http://www.prindlepost.org/wp-content/uploads/2015/06/Screen-Shot-2015-06-09-at-11.48.59-AM.png",
slug: "screen-shot-2015-06-09-at-11-48-59-am",
title: "",
description: "",
caption: "Image credit: Screenshot from Youtube",
parent: 4230,
mime_type: "image/png",
images: [ ]
},
{
id: 4235,
url: "http://www.prindlepost.org/wp-content/uploads/2015/06/Screen-Shot-2015-06-09-at-11.48.59-AM1.png",
slug: "screen-shot-2015-06-09-at-11-48-59-am-2",
title: "",
description: "",
caption: "Image/Youtube",
parent: 4230,
mime_type: "image/png",
images: [ ]
}
],
I had the same problem. After a few days melting my brain, I tried using Google's GSON. It does all the parsing and thinking for you, and returns a nice little object with all the information from the JSON.
Here's the project link: https://github.com/google/gson
To use it, you have to instantiate a new Gson parser, like so
Gson gson = new Gson();
YourObject object = gson.fromJson(jsonString, YourObject.class);
And the YourObject class should look something like this:
public class YourObject{
int status;
int count;
String count_total;
...
Post[] posts;
}
Now you create a Post class with the fields predicted in your JSON:
public class Post{
int id;
String type;
String slug;
...
Category[] categories;
}
I think you can get an idea on how to set up your POJO's. Keep in mind that, if you are getting an array of objects as your base object in JSON, be sure to use YourObject[] instead of YourObject when calling gson.fromJson
Just a heads-up: If any of the Json elements have a null or an empty value, even though they are primarily an int in your YourObject class, it is best to declare them as String to avoid java.lang.NumberFormatException.
You can use the method getJSONArray()

Categories

Resources