Nested JSON Objects in Kotlin with Volley - json

I am very new to this as you can probably tell, but i'm trying to parse a JSON url with Volley using Kotlin in Android Studio. The url contains nested Objects, not nested Arrays.
I can display everything inside "questionnaire", but I only want to display "typeOfQuestion". How do i do that?
MainActivity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
questionTV = findViewById(R.id.idTVQuestion)
answerTV = findViewById(R.id.idTVAnswer)
typeTV = findViewById(R.id.idTVType)
val queue: RequestQueue = Volley.newRequestQueue(applicationContext)
val request = JsonObjectRequest(Request.Method.GET, url, null, { response ->
loadingPB.setVisibility(View.GONE)
try {
val question: String = response.getString("question")
val answer: String = response.getString("answer")
val typeOfQuestion: String = response.getString("typeOfQuestion")
questionTV.text = question
answerTV.text = answer
typeTV.text = typeOfQuestion
} catch (e: Exception) {
e.printStackTrace()
}
}, { error ->
Log.e("TAG", "RESPONSE IS $error")
Toast.makeText(this#MainActivity, "Fail to get response", Toast.LENGTH_SHORT)
.show()
})
queue.add(request)
}
}
Heres the JSON:
{
"questionnaire": {
"question": "Where do you live?",
"answer": "In the mountains",
"typeOfQuestion": "Informative
}
}

You have object inside another json object.If you need to access field from child object you need to get child jsonObject and then get fields from object.
var questionnaire = response.getJSONObject("questionnaire")
You need to get fields from questionnaire object.Like.
val question: String = questionnaire.getString("question")
val answer: String = questionnaire.getString("answer")
val typeOfQuestion: String = questionnaire.getString("typeOfQuestion")

Related

JSONException Error upon parsing an array of values that I intend to handle in a RecyclerView

I am trying to parse a JSONArray using Volley and put it in a RecyclerView. I am stuck on this problem for days now and I really need some help. Everything seems to be fine but it just gives me a FATAL Exception regarding a JSONException. I have also used Postman and the Data Request seems to be fine. I just think there is a problem in what I am doing in my Kotlin code. Here is my code:
Get Data Function
val getLoanRecData = BASE_URL + "getLoanData"
val queue = Volley.newRequestQueue(this.activity)
val loanAppRequest = object : StringRequest(Method.POST,getLoanRecData,
Response.Listener { response ->
val jsonObject = JSONObject(response)
if(jsonObject.get("response").equals("Success"))
{
val jsonArray = jsonObject.getJSONArray("data")
for (i in 0..jsonArray.length()-1){
var jo = jsonArray.getJSONObject(i)
val id = jo.get("col_borrower_id").toString()
val loan_id = jo.get("col_loan_assignid").toString()
val loan_type = jo.get("col_loan_type").toString()
val due_date = jo.get("col_due_date").toString()
val loan_status = jo.get("col_status").toString()
val user = LoanRecordModel(id, loan_id, loan_type, loan_status, due_date)
list.add(user)
}
}else{
Toast.makeText(activity,jsonObject.get("response").toString(),Toast.LENGTH_SHORT).show()
}
}, Response.ErrorListener
{
error -> Toast.makeText(activity, error.toString(), Toast.LENGTH_SHORT).show()
}){
override fun getParams(): HashMap<String, String>
{
val sp = activity?.getSharedPreferences("user_data", Context.MODE_PRIVATE)
val emp_id = sp?.getString("user_id", "")
val map = HashMap<String,String>()
map["request"] = "SENT"
map["emp_id"] = emp_id.toString()
return map
}
}
queue.add(loanAppRequest)
I have also used serialized name on the data class for the RecyclerView because I thought the program only has problems regarding obtaining the data gathered from JSON, however that solved nothing and I still got the same error.
data class LoanRecordModel(
#SerialName("col_borrower_id")
val emp_id: String,
#SerialName("col_loan_assignid")
val loan_id: String,
#SerialName("col_loan_type")
val loan_type: String,
#SerialName("col_due_date")
val due_date: String,
#SerialName("col_status")
val loan_status: String)
These are the errors im getting:

Post Request in Kotlin

I'm trying to do a post request in Android Studio written in Kotlin
I'm posting a JSON object to our server and then the server is returning a JSON object back. But what I'm doing here is decoding the response body as a string and then converting it into the data structure we need. I'm sure there is a better and simpler way to do what I need done.
My current code works but the major issue I'm having is formatting the string if our objects have nested objects which is why I want to figure out a better way to turn the response body into a json object.
I'm not too familiar with many request libraries for kotlin but I have looked into okhttp3 but I'm not sure how to post a json object, attach headers and decode the response body into a json object.
I know for okhttp3 I need to convert the json object to a string to post other than that I'm lost.
Breakdown of what's needed:
Post JSON Object To Server
Send Headers With Post Request
Decode Response Body into JSON Object/ Kotlin Equivalent
Simplify What I'm Trying to Do if Possible
This is the current code I have
private fun postRequestToGetDashboardData() {
val r = JSONObject()
r.put("uid", muid)
r.put("token", mtoken)
SendJsonDataToServer().execute(r.toString());
}
inner class SendJsonDataToServer :
AsyncTask<String?, String?, String?>() {
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
if (result.equals(null)) {
val t = Toast.makeText(this#Home, "No devices to display", Toast.LENGTH_LONG)
t.setGravity(Gravity.CENTER, 0, 0)
t.show()
} else {
intentForUnique.putExtra("FirstEndpointData", result)
var list = handleJson(result)
adapter.submitList(list)
dashboardItem_list.adapter = adapter
adapter.notifyDataSetChanged();
dashboardItem_list.smoothScrollToPosition(0);
}
}
override fun doInBackground(vararg params: String?): String? {
val JsonDATA = params[0]!!
var urlConnection: HttpURLConnection? = null
var reader: BufferedReader? = null
try {
val url = URL("URL");
urlConnection = url.openConnection() as HttpURLConnection;
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", mtoken);
urlConnection.setRequestProperty("Accept", "application/json");
val writer: Writer =
BufferedWriter(OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
writer.write(JsonDATA);
writer.close();
val inputStream: InputStream = urlConnection.getInputStream();
if (inputStream == null) {
return null;
}
reader = BufferedReader(InputStreamReader(inputStream))
var inputLine: String? = reader.readLine()
if (inputLine.equals("null")) {
return null
} else {
return inputLine
}
} catch (ex: Exception) {
Log.e(TAG, "Connection Failed", ex);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (ex: Exception) {
Log.e(TAG, "Error closing stream", ex);
}
}
}
return null
}
}
private fun handleJson(jsonString: String?): ArrayList<SensorData> {
val jsonArray = JSONArray(jsonString)
val list = ArrayList<SensorData>()
var x = 0
while (x < jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(x)
list.add(
SensorData(
jsonObject.getInt("deviceId"),
// jsonObject.getString("deviceName"),
jsonObject.getInt("battery"),
jsonObject.getString("dateTime"),
jsonObject.getInt("airValue"),
jsonObject.getInt("waterValue"),
jsonObject.getInt("soilMoistureValue"),
jsonObject.getInt("soilMoisturePercent")
)
)
x++
}
return list
}
So the json data being returned back is an array of this structure (our backend is written in Go)
type Device struct {
DeviceID int `bson:"deviceId" json:"deviceId"`
Battery int `bson:"battery" json:"battery"`
DateTime time.Time `bson:"dateTime" json:"dateTime"`
AirValue int `bson:"airValue" json:"airValue"`
WaterValue int `bson:"waterValue" json:"waterValue"`
SoilMoistureValue int `bson:"soilMoistureValue" json:"soilMoistureValue"`
SoilMoisturePercent int `bson:"soilMoisturePercent" json:"soilMoisturePercent"`
}

Same SerializedName for two different data types sometimes, but with Kotlin

The JSON file I'm pulling from unfortunately has a node with the same variable name but could have two different data types randomly. When I make a network call (using gson) I get the error:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a BEGIN_ARRAY but was int at line 1 column 5344 path $[1].medium
the JSON looks like
{
"title": "Live JSON generator",
"url": google.com,
"medium": ["chicken", "radio", "room"]
}
//However sometimes medium can be:
"medium": 259
My Serialized class looks like:
data class SearchItem(
#SerializedName("title") var title: String,
#SerializedName("url") var urlStr: String,
#SerializedName("medium") val medium: List<String>? = null
) : Serializable {}
The way I'm making the network call is like this:
private val api: P1Api
fun onItemClicked(searchItem: SearchItem) {
api.getCollections { response, error ->
response.toString()
val searchItems: List<SearchItem> = Util.gson?.fromJson<List<SearchItem>>(
response.get("results").toString()
, object : TypeToken<List<SearchItem>>() {}.type)?.toList()!!
...
doStuffWithSearchItems(searchItems)
}
How do I handle both cases where "medium" can either be an array of strings or it could be an Int?
You could write custom JsonDeserializer for this case:
class SearchItemCustomDeserializer: JsonDeserializer<SearchItem> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): SearchItem {
val obj = json.asJsonObject
val title = obj.get("title").asString
val url = obj.get("url").asString
val mediumProp = obj.get("medium")
val medium = if(mediumProp.isJsonArray) {
mediumProp.asJsonArray.map { it.asString }
} else {
listOf(mediumProp.asString)
}
return SearchItem(
title = title,
urlStr = url,
medium = medium
)
}
}
With this class you "manually" deserialize json to object. For medium property we check is this array or simple json primitive with function mediumProp.isJsonArray. And if answer is yes - then deserialize field as json array of strings mediumProp.asJsonArray.map { it.asString } Else deserialize the field as string.
And then we register our custom SearchItemCustomDeserializer on GsonBuilder using method registerTypeAdapter
val gson = GsonBuilder()
.registerTypeAdapter(SearchItem::class.java, SearchItemCustomDeserializer())
.create()
And after this you can use this gson instance to deserialize yours objects

Why does my Gson object keep returning null?

I'm trying to parse JSON data to a class but gson.fromJson(response, bitt::class.java) keeps returning null.
class bitt(#SerializedName("result")val result: String) {
val someVal: String = "string"
fun method() {
print("something")
}
}
val response: String = "{'success':true,'message':'','result':'Im a sult'}"
println(response)
val gson = Gson()
val ticker = gson.fromJson(response, bitt::class.java)
println(ticker)
What am I doing wrong here?
JSON always uses double quotes ", not single quotes '. Your response uses single quotes, so it is not valid JSON.
As in many other languages, you can use \" to put a double quote in a string literal:
val response: String = "{\"success\":true,\"message\":\"\",\"result\":\"I'm a result\"}"
change to Data Class instead of Class
example from your code:
data class bitt(val result: String = "") {
val someVal: String = "string"
fun method() {
print("something")
}
}
I guess it takes long time before you get the result back
so the ticker still remain null
you can use kotlin coroutines to handle it.
or simply use callback like this
data class bitt(val result: String = "") {
val someVal: String = "string"
fun method() {
print("something")
}
}
fun getTicker(response: String, onComplete: (bitt) -> Unit) {
val ticker = Gson().fromJson(response, bitt::class.java)
onComplete(ticker)
}
val response: String = "{'success':true,'message':'','result':'Im a sult'}"
println(response)
getTicker(response){ println(it) }
then you might need to use Coroutine
https://github.com/Kotlin/kotlinx.coroutines
data class bitt(val result: String = "") {
val someVal: String = "string"
fun method() {
print("something")
}
}
suspend fun getTicker(response: String) = Gson().fromJson(response, bitt::class.java)
fun yourMethod() {
val response: String = "{'success':true,'message':'','result':'Im a sult'}"
println(response)
CoroutineScope(IO).launch {
val ticker = getTicker(response)
println(ticker)
}
}
KotlinConf 2017 - Introduction to Coroutines by Roman Elizarov

How to add Body request in url in Kotlin?

Here is my Code that for Volley Request:-
val searchRequest = object : JsonArrayRequest(Request.Method.GET,url,
Response.Listener { response ->
val result = response.toString()
},
Response.ErrorListener { error ->
Toast.makeText(activity, "Error!",Toast.LENGTH_LONG)
.show()
Log.d("ERROR",error.toString())
})
{
override fun getBody(): ByteArray {
// TODO add Body, Header section works //////////
return super.getBody()
}
override fun getBodyContentType(): String {
return "application/json"
}
override fun getHeaders() : Map<String,String> {
val params: MutableMap<String, String> = HashMap()
params["Search-String"] = songName
params["Authorization"] = "Bearer ${accessTx.text}"
return params
}
}
AppController.instance!!.addToRequestQueue(searchRequest)
I want to add this information in the body section
video_id = "BDJIAH" , audio_quality = "256"
here is the sample to add above information in the below segment.
{ "video_id":"ABCDE", "audio_quality":"256" }
Basically, I am facing problem in ByteArray section. That doesn't work for me.
You can use toByteArray() method of String class in Kotlin.
For example:
val charset = Charsets.UTF_8
val byteArray = "SomeValue".toByteArray(charset)
Also try to pass multiple values in the request body in this way:
val requestBody = "video_id = "+"ABCDE"+ "& audio_quality ="+ "256"
val charset = Charsets.UTF_8
val byteArray = requestBody.toByteArray(charset)