I have a data class called Model,
data class Model(var name: String? = null, var address: String? = null) {
override fun toString(): String {
return "name: $name address: $address"
}
}
I have two json strings,
val json1 = "{ \"name\": \"Alex\", \"address\": \"rome, 1000\" }"
val json2 = "{ \"name\": \"Alex\", \"address\": {\"city\": \"rome\", \"post\": \"1000\" } }"
Below conversion is working fine,
val model1 = Gson().fromJson<Model>(json1, Model::class.java)
But, this version does not work. Giving an exception.
val model2 = Gson().fromJson<Model>(json2, Model::class.java)
Exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.achellies.kotlin, PID: 11211
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.achellies.kotlin/com.achellies.kotlin.MainActivity}: com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 40 path $.address
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3086)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3229)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at
How can i parse json2 so that address field will be holding the string value of the inner address json data? For example after parsing address should be equal to
{"city": "rome", "post": "1000"}
Did you try with the Any?
data class Model(var name: String? = null, var address: Any? = null) {
override fun toString(): String {
return "name: $name address: $address"
}
}
Try with this, it will work.
Have a good day. :)
You may use a JsonDeserializer specialized for your Model class, like so:
fun main() {
val json1 = "{ \"name\": \"Alex\", \"address\": \"rome, 1000\" }"
val json2 = "{ \"name\": \"Alex\", \"address\": {\"city\": \"rome\", \"post\": \"1000\" } }"
val gson = GsonBuilder().registerTypeAdapter(Model::class.java, ModelDeserializer()).create()
println(gson.fromJson(json1, Model::class.java))
println(gson.fromJson(json2, Model::class.java))
}
data class Model(val name: String, val address: String)
class ModelDeserializer : JsonDeserializer<Model> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Model {
json as JsonObject
val name = json.get("name").asString
val addressJson = json.get("address")
val address = if (addressJson.isJsonObject) addressJson.asJsonObject.toString() else addressJson.asString
return Model(name, address)
}
}
Which outputs:
Model(name=Alex, address=rome, 1000)
Model(name=Alex, address={"city":"rome","post":"1000"})
If you use the Kotson library, which seems you are, things get even better:
val gson = GsonBuilder()
.registerTypeAdapter<Model> {
deserialize {
val json = it.json.asJsonObject
val name = json["name"].asString
val addressJson = json["address"]
val address = if (addressJson.isJsonObject) addressJson.asJsonObject.toString() else addressJson.asString
Model(name, address)
}
}
.create()
println(gson.fromJson<Model>(json1))
println(gson.fromJson<Model>(json2))
Please try with Any, it should work.
var address: String? = null
If address is a valid json by it self then you can do this:
val jsonObject = JSONObject(jsonString)
val address = jsonObject.optJSONObject("address").toString()
If it is a string then
val address = jsonObject.optString("address").toString()
Related
I have the following json generated from a WordPress Post:
{
"events":[
{
"id":4651,
"title":"Test title",
"description":"testttt",
"image": {
url: "https://myhost.tv/wp-content/uploads/2019/07/event2.jpg",
id: "4652"}
}
]
}
My model for json is as follows:
data class EventsFeed(val events: Array<Events>)
data class Events (
val id : Int,
val title : String,
val description : String,
val image : Image
)
data class Image (
val url : String,
val id : Int,
)
I do the parsing with Json and everything works fine, but when I do a POST in wordpress and I don't put an image, the value of the Image key puts me as afalse, like this:
{
"events":[
{
"id":4651,
"title":"Stand Up Comedy",
"description":"testttt",
"image":false
}
]
}
And just because image has the valuefalse, the parsing returns an error: Expected BEGIN_OBJECT but was BOOLEAN at line 1 column 5781 path $ .events [1] .image
What can I do so that when the post has no image to parse correctly ignoring the value false or in any case if it isfalse please keep it a default image (https://myhost.com/image_defaul.jpg)
The json is generated by the plugin for Wordpress: The Events Calendar: Demo json here
My function (usen Volley and Gson) for parsing is as follows (the data array is sent to an adapter for display in a recyclerview)
fun jsonObjectRequest() {
Log.i(LOG_TAG, "jsonObjectRequest")
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://myhost.tv/wp-json/tribe/events/v1/events"
// Request a JSONObject response from the provided URL.
val jsonObjectRequest = JsonObjectRequest( url, null,
Response.Listener { response ->
Log.i(LOG_TAG, "Response is: $response")
val gson = Gson()
val homeEvents = gson.fromJson(response.toString(), EventsFeed::class.java)
activity?.runOnUiThread {
recyclerEvents.adapter = AdaptadorEventos(homeEvents)
}
},
Response.ErrorListener { error ->
error.printStackTrace()
Log.e(LOG_TAG, "That didn't work!")
}
)
// Add the request to the RequestQueue.
queue.add(jsonObjectRequest)
}
The reason why your post method is not getting the Image Class object is because of the json is not a valid json, You may validate it on https://jsonlint.com/. The reason is: "url" and "id" keys are not surrounded by "". Take a look at the below solution, it works perfectly:
package com.example.myapplication
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.gson.Gson
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getEvemtsObject()
}
private val jsonString = " {\n" +
" \t\"events\": [{\n" +
" \t\t\"id\": 4651,\n" +
" \t\t\"title\": \"Test title\",\n" +
" \t\t\"description\": \"testttt\",\n" +
" \t\t\"image\": {\n" +
" \t\t\t\"url\": \"https://myhost.tv/wp-content/uploads/2019/07/event2.jpg\",\n" +
" \t\t\t\"id\": \"4652\"\n" +
" \t\t}\n" +
" \t}]\n" +
" }"
private fun getEvemtsObject() {
val gson = Gson()
System.out.println("from_gson ---> " + gson.fromJson<EventsFeed>(jsonString,EventsFeed::class.java))
}
}
You can use custom Gson deserializer like this:
class EventsDeserializer : JsonDeserializer<Events> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Events {
val jsonObject = json.asJsonObject
return Events(
id = jsonObject.get("id").asInt,
title = jsonObject.get("title").asString,
description = jsonObject.get("description").asString,
image = parseImage(context, jsonObject.get("image"))
)
}
private fun parseImage(context: JsonDeserializationContext, json: JsonElement): Image =
try {
context.deserialize(json, Image::class.java)
} catch (_: Throwable) {
Image("https://myhost.com/image_defaul.jpg", 0)
}
}
and here is the test of your json:
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Events::class.java, EventsDeserializer())
.create()
val json = """
{
"events": [
{
"id": 4651,
"title": "Stand Up Comedy",
"description": "testttt",
"image": false
}
]
}
""".trimIndent()
val events = gson.fromJson(json, EventsFeed::class.java)
}
the image parameter in constructor should be nullable.
rewrite your class like this
data class Events (
val id : Int,
val title : String,
val description : String,
var image : Image? = null)
and change your API to send null instead of false.
This might also be an option (but fixing the API is the better option):
val image: Any
This is what my json looks like
{
"sub": "9",
"auth_time": 1559381757,
"idp": "idsrv",
"role": [
"Employer",
"Employee",
"Student"
],
"iss": "",
"aud": "",
"exp": 1574933757,
"nbf": 1559381757
}
This is the object I want to convert this Json into.
data class Claims (
#SerializedName("nameid") val nameId: String,
#SerializedName("unique_id") val uniqueId: String,
#SerializedName("sub") val sub: String,
#SerializedName("unifiedNumber") val unifiedNumber: String,
#SerializedName("role") var roleList: List<Role>
)
I wrote a custom Deserializer (which works in Java) for the List type
class RoleDeserializer : JsonDeserializer<List<Role>> {
private var roleId = 0
#Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): MutableList<Role> {
val resultList = ArrayList<Role>()
if (json.isJsonArray) {
for (e in json.asJsonArray) {
resultList.add(Role(id = roleId++, name = e.asString))
}
} else if (json.isJsonObject) {
resultList.add(Role(id = roleId++, name = json.asString))
} else if (json.isJsonPrimitive) {
if ((json as JsonPrimitive).isString)
resultList.add(Role(id = roleId++, name = json.getAsString()))
} else {
throw RuntimeException("Unexpected JSON type: " + json.javaClass)
}
return resultList
}
}
This is how I register my type adapter
val listType: Type = object : TypeToken<List<Role>>() {}.type
val gson = GsonBuilder().registerTypeAdapter(listType, RoleDeserializer()).create()
val claims = gson.fromJson(stringJson, Claims::class.java)
I still get a parse exception stating that
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 161 path $.role[0]
and my RoleDeserializer is never called. Am I doing something wrong while registering the type adapter?
Try to replace
val listType: Type = object : TypeToken<List<Role>>() {}.type
with
val listType: Type = object : TypeToken<MutableList<Role>>() {}.type
The role is String array in JSON
Use this
#SerializedName("role") var roleList: List<String>
Instead of this
#SerializedName("role") var roleList: List<Role>
Try this
data class Claims (
#SerializedName("nameid") val nameId: String,
#SerializedName("unique_id") val uniqueId: String,
#SerializedName("sub") val sub: String,
#SerializedName("unifiedNumber") val unifiedNumber: String,
#SerializedName("role") var roleList: List<String>
)
I have one issue about code data class kotlin android.
How to implement server response? sometimes I get String value or sometime get Object class.
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
val data: String as Data
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
When I implement only Data class it works, like this val data: Data or val data: String. But I need together Data and String with key only data.
Is it possible?
When having multiple type for same variable, we can use Any type which is equivalent to Object type in java. So solution is like below :
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
var data: Any? = null // changed it to var from val, so that we can change it's type runtime if required
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
And when accessing that variable, one can simply cast like below :
val apiResponse : CMSRespApi //= some API response here from network call
when (apiResponse.data) {
is String -> {
// apiResponse.data will be smart-casted to String here
}
else -> {
val responseData = Gson().fromJson<CMSRespApi.Data>(
Gson().toJsonTree(apiResponse.data),
CMSRespApi.Data::class.java
)
}
}
After 12 Hrs spend and got the solution my self,
val getResultCon = getSerCont.result // response Any
val gson = Gson()
val jsonElement = gson.toJsonTree(getResultCon)
val resultData = gson.fromJson(jsonElement, SearchContactApi.Result::class.java)
Convert your data string to toJsonTree and fromJson with model class then got result.
I have following groups of string and would like to create a string following the syntax: substr1.substr2.substr3 = substr1 + substr2 + substr3
data class Protocol( val http: String = "http://",
val https: String = "https://")
data class BaseUrl( val baseProd: String = "url_prod",
val baseDev: String = "url_dev",
val baseTest: String = "url_test")
data class ApiEndpoint( val GET_USER: String = "endpoint_get_user",
val LOGIN: String = "endpoint_login",
val REGISTER: String = "endpoint_get",
val FETCH_HISTORY: String = "endpoint_fetch_history")
data class WebUrl( val HOME: String = "path_home",
val BALANCE: String = "path_balance",
val MANAGE_SUBSCRIPTION: String = "path_manage_subscription")
data class RequestEnvironment( val mobile: String = "query_mobile",
val desktop: String = "query_desktop")
My goal is to make something that build the strings like this
UrlFactory.https.baseDev.GET_USER //result: https://url_dev/get_user
UrlFactory.https.baseProd.HOME.mobile //result: https://url_prod/home?mobile=1
UrlFactory.http.baseDev.BALANCE //result: http://url_dev/balance
Have anyone built a nice way to handle url strings dynamically like this?
You can create an object Url which is in charge of building up your URL string.
You can build up Url by passing in your selected options through the constructor.
When the object is constructed, you can then call toString which will concatenate and return the values together.
class Url ( var protocol : Protocol,
var baseUrl : BaseUrl,
var apiEndpoint : ApiEndpoint,
var webUrl : WebUrl,
var requestEnvironment : RequestEnvironment) {
override fun toString() : String {
return protocol.value +
baseUrl.value +
apiEndpoint.value +
webUrl.value +
requestEnvironment.value
}
}
To add another level of safety when working with your Strings, I took the liberty of converting them to enums. This will give you the benefit of allowing you to limit the possible values which can be set:
enum class Protocol(val value : String) {
HTTP("http://"),
HTTPS("https://")
}
enum class BaseUrl(val value : String) {
BASE_PROD("url_prod"),
BASE_DEV("url_dev"),
BASE_TEST("url_test")
}
enum class ApiEndpoint(val value : String) {
GET_USER("endpoint_get_user"),
LOGIN("endpoint_login"),
REGISTER("endpoint_get"),
FETCH_HISTORY("endpoint_fetch_history")
}
enum class WebUrl(val value : String) {
HOME("path_home"),
BALANCE("path_balance"),
MANAGE_SUBSCRIPTION("path_manage_subscription")
}
enum class RequestEnvironment(val value : String) {
MOBILE("query_mobile"),
DESKTOP("query_desktop")
}
Finally, here is an example of how you can now build your URL:
fun main() {
val url : Url = Url(Protocol.HTTP,
BaseUrl.BASE_DEV,
ApiEndpoint.GET_USER,
WebUrl.HOME,
RequestEnvironment.MOBILE);
println(url.toString())
}
I am making an API request which returns some array values. I need to serialize these array values so that I can assign them to their corresponding class attributes (which are String types).
Now I know how to use GSON to serialize and deserialize lists, but with Retrofit the mapping is done automatically. This means that if my attribute is of type String, the API call returns the error "Expected a String but received an Array instead". How do I get around this so that I can receive them as arrays without failure, and them store them as strings subsequently?
My API Response:
{
"utterances": [{
"langs": ["eng", "afr", "xho", "zul"],
"utts": [
"Have you been here before?",
"Was u al hier gewees?",
"Ingaba wakhe weza apha ngaphambili?",
"Ingabe uke weza lapha ngaphambilini?"
],
"responses": [
["Yes", "No"],
["Ja", "Nee"],
["Ewe", "Hayi"],
["Yebo", "Cha"]
]
},
{
"langs": ["eng", "afr", "xho", "zul"],
"utts": [
"How are you?",
"Hoe gaan dit met jou?",
"unjani?",
"unjani?"
],
"responses": [
["Good", "Bad"],
["Goed", "sleg"],
["ezilungileyo", "ezimbi"],
["kuhle", "kubi"]
]
}
]
}
My UtteranceResponse class:
class UtteranceResponse {
#SerializedName("status")
var status: String? = null
#SerializedName("count")
var count: Int = 0
#SerializedName("utterances")
var utterances: ArrayList<Utterance>? = null
}
My Utterance class:
class Utterance: SugarRecord {
#SerializedName ("langs")
var langs: String? = null
#SerializedName ("utts")
var utterances_text: String? = null
var utterances_tts: String? = null
#SerializedName ("responses")
var responses_text: String? = null
constructor(){
}
}
And finally the calling function:
fun getUtterancesFromWebservice (){
val apiService = ApiInterface.create()
val call = apiService.getUtteranceDetails()
call.enqueue(object: Callback<UtteranceResponse> {
override fun onResponse(call: Call<UtteranceResponse>, response: retrofit2.Response<UtteranceResponse>?) {
if (response != null) {
if (response.body()?.utterances != null){
var list: List<Utterance> = response.body()?.utterances!!
val utterances: Utterance = list[0]
//storeUtterancesFromList(list)
} else {
Log.d ("Response:", response.body().toString())
}
}else{
Log.d ("responseResult", "NULL")
}
}
override fun onFailure(call: Call<UtteranceResponse>, t: Throwable) {
Log.e("SHIT", t.toString())
}
})
}
UPDATE
My API Interface as well:
#GET("bins/1ahazo")
abstract fun getUtteranceDetails():Call<UtteranceResponse>
companion object Factory {
const val BASE_URL = "https://api.myjson.com/"
fun create(): ApiInterface {
val gson = GsonBuilder().setPrettyPrinting().create()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(ApiInterface::class.java)
}
}
You are returning single object not list. Change Call<UtteranceResponse> in ApiInterface to
Call<List<Utterance>>
and for converting list to string list to string and string to list
class Utterance: SugarRecord {
#SerializedName ("langs")
var langs: List<String?>? = null
#SerializedName ("utts")
var utterances_text: String? = null
var utterances_tts: List<String?>? = null
#SerializedName ("responses")
var responses_tex:List<List<String?>?>? = null;
constructor(){
}
}