2

My JSON Resoponse looks like-

{
    "body": {
        "count": 4,
        "sender": "margarete20181570"
    },
    "inserted_at": "2020-05-07T05:48:14.465Z",
    "type": 1
},
{
    "body": "savanna19562530 hit the SOS button!",
    "inserted_at": "2020-05-06T09:17:36.658Z",
    "type": 2
}

And I am using the Data Class like below to parse the above JSON, what is wrong here!

data class Notification(val body: String, val inserted_at: String, val type: Int) {

constructor(
    msgBody: MessageNotification,
    inserted_at: String,
    type: Int
) : this(msgBody.sender + "Sent you " + msgBody.count + "Messages", inserted_at, type)

}

But this dosent work it gives parsing error like - Expected String , got object

My Api call looks like-

@GET("notifications")
suspend fun getNotifications(
    @HeaderMap headers: HashMap<String, String>
): Response<List<Notification>>

The main objective is how to remodel the code such that the Notification model class' different constructor will be called on different cases such that it does not give such error expecting string, got object or expecting object got string

How should I improve my code to parse the response?

Any help is appreciated!

Achy97
  • 994
  • 1
  • 14
  • 29
  • 1
    `body` field in your JSON response is object in first and string in second. That may be the case you are getting error. – Hussain May 07 '20 at 08:28
  • yes I know the error, that is my question , that how to remodel my data class to catch both the types, and thats why i have introduced secondary constructor also – Achy97 May 07 '20 at 08:29
  • Which lib do you use for deserialization? – Animesh Sahu May 07 '20 at 08:40
  • I am just using Retrofit and deserializing using data classes manually – Achy97 May 07 '20 at 08:41
  • So you are using reflection, right? Then you can try catch on calling the [constructors](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/constructors.html) defined. – Animesh Sahu May 07 '20 at 08:43
  • yes basically what I understood , that although I defined the secondary consstructor, it is not being called at all, how can I try catch to conditionally call it or can it be done using a normal class – Achy97 May 07 '20 at 08:45
  • Can I use Normal Class to be used as model in kotlin? – Achy97 May 07 '20 at 09:33
  • oh my bad, initially I am using GSON to get the objects from the API , I got @AnimeshSahu your question wrongly – Achy97 May 07 '20 at 09:42
  • 1
    Does this answer your question? [How to handle different data types with same attribute name with Gson?](https://stackoverflow.com/questions/31758872/how-to-handle-different-data-types-with-same-attribute-name-with-gson) – Animesh Sahu May 07 '20 at 10:14
  • @Achy97 GSON works using reflection, it doesn't call any construction at all. _Is there a possiblity I can use normal class like java, instead of data class, and overlload the constructors and use getter and setter?_ - What is the final, expected behaviour that you're trying to achieve? Does your API return `body` sometimes as an object and sometimes as a string, or is it always an object and you just want to have it nicely processed to a single string to display it later in your app? – jsamol May 07 '20 at 10:25
  • sometimes as string and sometimes as object @jsamol – Achy97 May 07 '20 at 10:28
  • You have to parse it manually then (as already suggested) by registering your own type adapter. [Here](https://stackoverflow.com/a/44173009/11342519) and [here](https://stackoverflow.com/a/42679311/11342519) are some examples. – jsamol May 07 '20 at 10:45

2 Answers2

1

Since you are deserializing the JSON manually, this can a be solution you can try

data class Body(val count: Int, val sender: String)

data class Notification(val body: Any, val insertedAt: String, val type: Int)

Now, Parsing the JSON response

val jsonResponse = JSONArray(/*JSON response string*/) // I am guessing this is an array

    (0 until jsonResponse.length()).forEach {
        val jsonObj = jsonResponse.getJSONObject(it)
        val jsonBody = jsonObj.get("body")
        if (jsonBody is String) {
            // Body field is a String instance
            val notification = Notification(
                body = jsonBody.toString(),
                insertedAt = jsonObj.getString("inserted_at"),
                type = jsonObj.getInt("type")
            )
            // do something
        } else {
            // Body field is a object
            val jsonBodyObj = jsonObj.getJSONObject("body")
            val body = Body(
                count = jsonBodyObj.getInt("count"),
                sender = jsonBodyObj.getString("sender")
            )
            val notification = Notification(
                body = body,
                insertedAt = jsonObj.getString("inserted_at"),
                type = jsonObj.getInt("type")
            )

            // do something
        }
    }

I hope this helps or atleast you get an idea how you approach to solve your problem. You can also check Gson exclusion strategy.

Hussain
  • 1,243
  • 12
  • 21
  • actually it is pretty complex as I am using data binding in recycleview and assigning the object as `override fun onBindViewHolder(holder: NotificationAdapter.NotificationViewHolder, position: Int) { holder.notificationListItemBinding.notifyItem = notificationList[position] }` thats why it would be better if i could directly call the constructor and retrieve the object – Achy97 May 07 '20 at 09:29
  • i have added the api call , I cannot use that json destructuring @Hussain – Achy97 May 07 '20 at 09:32
  • Even if you are using view binding, you can make use of conditional binding or custom setters – Hussain May 07 '20 at 09:35
  • as i called the api and accepted response with above said data class, it gives error , so please tell what should I put in here `: Response>` in place of Notification class as it is automatically trying to type cast into the given model – Achy97 May 07 '20 at 09:38
  • sorry, I am using GSON to get the objects, and assigning them , misunderstood initially about what library I am using question – Achy97 May 07 '20 at 09:44
  • 1
    You can take `body` as `Any`, after you receive the response type cast according to you need – Hussain May 07 '20 at 10:17
  • ok i wil try that, but is there a possiblity I can use normal class like java, instead of data class, and overlload the constructors and use getter and setter ? @Hussain – Achy97 May 07 '20 at 10:19
0

The body field in your JSON is an object and should map to an object defined in your project. You could have a class header like the following for that purpose:

data class Body(val count: Int, val sender: String)

Your Notification class header would then have a Body field to capture that part of your JSON response like so:

data class Notification(val body: Body, val inserted_at: String, val type: Int)

I generally use Gson for deserialization of Retrofit responses. It works really well and is easy to customize. Let me know if something needs clarification.

danmaze
  • 319
  • 3
  • 8
  • i think this is the same as the deleted answer – Achy97 May 07 '20 at 09:12
  • I didn't know there was a deleted answer. Have you tried this? – danmaze May 07 '20 at 09:43
  • yes its giving error , `expecting string , got object` – Achy97 May 07 '20 at 09:44
  • Where exactly do you get that error? I'm guessing it's because of the second object in your JSON response. In the first object, `body` is an object whereas in the second, `body` is a string. If you have control over the network API, I suggest you think some more about the body entity so that you can make your format consistent. It seems your `body` object could use three fields: `count`, `sender`, and `message`. Of course, I'm only speculating as you should know better. – danmaze May 07 '20 at 10:03
  • yes I can change the API JSON type , but i just wante to explore if it is possible – Achy97 May 07 '20 at 10:05
  • It's possible but you would have to do custom serialization. I'm not sure I'd want to do that if I can avoid it. On the other hand, it might make sense for your application if those fields are going to be empty in most cases. – danmaze May 07 '20 at 10:11
  • as there is 2 constructor defined-> is there not a methodology that ,enables us to use different constructor on different json object- that is the goal, ie utilizing the seccondary constructor – Achy97 May 07 '20 at 10:13
  • Again, you can achieve anything you want if you handle the serialization yourself, for example, based on which type of notification is received. Do you mind clarifying the difference between the two notification types? – danmaze May 07 '20 at 10:19
  • ya , one is simple body and another is a message with sender and type object – Achy97 May 07 '20 at 10:21