Avinash Kumawat
Avinash Kumawat

Reputation: 23

com.google.gson.internal.LinkedTreeMap cannot be cast to my custom class

I am getting:

com.google.gson.internal.LinkedTreeMap cannot be cast to Message class

I want to convert a any type object to my custom class. I am trying gson to convert the object to JSON and then JSON string to my Message class.

I created a function with generic type conversion with Gson.

//Custom Class
data class Message(
    @SerializedName("content")
    val content: String? = null
)

//My abstract class

abstract class EchoListener<T> {
    companion object {
        private val TAG = EchoListener::class.java.simpleName
    }

    abstract fun onDataReceived(data: T?)

    fun submitData(it: Any) {
        Log.e(TAG, "Echio data  ${it}")
        val gson = Gson()
        val json = gson.toJson(it)
        val data: EchoData<T> = gson.fromJson(json, genericType<EchoData<T>>())
        Log.e(TAG, "Converted data ${data.data}")
        onDataReceived(data.data)
    }
}

inline fun <reified T> genericType() = object : TypeToken<T>() {}.type


class SomeClass {
    fun <T> listen(event: String, callback: EchoListener<T>) {
        val listener = Emitter.Listener {
            Log.e(TAG, "Data ${it[1]}")
            if (it.size > 1)
                callback.submitData(it[1])
        }
        this.socket.on(event, listener)
        this.bind(event, listener)
    }
}


//Main Activity
someClassObj?.listen("chat", object : EchoListener<Message>() {
    override fun onDataReceived(data: Message?) {
        Log.e(TAG, "Message ${data}")
    }
})

Edited

Here is the link to the library that I am creating: Github repo

Here is the sample android application Android Github Repo

Upvotes: 2

Views: 3791

Answers (2)

Abed
Abed

Reputation: 5197

This can be done using kotlinx.serialization in a fairly simple way. First, see an exmple to have an idea of how it works, then add the library dependency to the app build.gradle:

implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0"

Note, to be able to use Kotlin serialization library in your Kotlin code, make sure that your Kotlin compiler version is 1.3.30 or higher.

Now you need to annotate all the classes that you want to serialize/deserialize with @Serializable annotation and make sure you are importing from kotlinx.serialization.Serializable.

After you annotate your classes the compiler will auto-generate a static function in the Java byte code called serializer() for each class, we will be calling this function to serialize and deserialize an object of that class.

Now to implement a generic serialize/deserialize function, add this file to your project:

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.Json

object SerializationManager {

    fun <T> deserializeObject(
        json: String,
        deserializationStrategy: DeserializationStrategy<T>
    ): T = Json.parse(deserializationStrategy, json)


    fun <T> getList(json: String, dataSerializer: KSerializer<List<T>>): List<T> =
        Json.parse(dataSerializer, json)



    fun <T> serializeObject(data: T, serializationStrategy: SerializationStrategy<T>): String =
        Json.stringify(serializationStrategy, data)


    fun <T> serializeList(
        data: List<T>,
        dataSerializer: KSerializer<List<T>>
    ): String = Json.stringify(dataSerializer, data)

}

An example of how to use SerializationManager

I have a Reciter class that I want to serialize:

import kotlinx.serialization.Serializable

@Serializable
data class Reciter(
    val id: String,
    val name: String,
    val servers: String
)

then in your Activity or elsewhere:

 val reciter = Reciter("A123", "ABCD", "https://stackoverflow.com")
 val json1 = SerializationManager.serializeObject(reciter, Reciter.serializer())


 // If we want to serialize a list of reciters, then we pass Reciter.serializer().list
 val recitersList = listOf(
     Reciter("A123", "ABCD", "https://stackoverflow.com"),
     Reciter("B456", "F.abc", "https://stackoverflow.com"),
     Reciter("M568", "NDK", "https://stackoverflow.com")
 )
 val json2 = SerializationManager.serializeList(recitersList,Reciter.serializer().list)

Upvotes: 1

Pavlo Ostasha
Pavlo Ostasha

Reputation: 16719

You try to use it in very complex way. It can be so much easier with Gson.

Here is four extension methods that will cover almost all of the cases you need. If no - I hope the rest you will figure out based on these.

inline fun <reified T> T.toJson(): String = Gson().toJson(this)

inline fun <reified T> String.toObject(): T = Gson().fromJson(this, T::class.java)

inline fun <reified T, K, V> Map<K, V>.toObject(): T = Gson().fromJson(this.toJson(), T::class.java)

// and more classic but less used
fun <T> String.toObject(type: Type): T = Gson().fromJson(this, type)

Now you can use it like

data class SomeClass(val today: Int, val yesterday: Int)

val someClass = SomeClass(3,4)
val json = someClass.toJson() // { "today" : 3,"yesterday" : 4 }
val someClassDesetialized = json.toObject<SomeClass>() // SomeClass(3,4)

val map: Map<String, Int> = mapOf("today" to 2,"yesterday" to 1)
val mapJson= map.toJson() // { "today" : 2,"yesterday" : 1 }
val someClassFromMap = map.toObject<SomeClass, String, Int>() // SomeClass(2,1)
val someClassFromMapJson: SomeClass = mapJson.toObject<SomeClass>().copy(5) // SomeClass(5,1)

...

val data: EchoData<T> = json.toObject()
val data1 = json.toObject<EchoData<T>>()

Something like that. Note that Gson lib uses Java serialization so it maybe slower and less secure than Kotlin specific kotlinx.serialization but it is not in prealpha version as kotlinx.serialization so it is less bug prone and more mature.

Edit1

here is how to hide the conversion in quite the industry standard way.

inline fun <reified T> Map<String, Any>.convert(): T = toObject() 

and then use it like

map.convert<SomeClass>()

it it better than pass the Type as method argument.

You will have to adjust this methods to be usable from the Java code though. Here it is explained how to do it.

Edit2

Here is an architectural issue your app was having fixed

SomeClass from your example should be

class SomeClass {
        inline fun <reified T> listen(event: String, callback: (T) -> Unit) {
            Emitter.Listener {
                Log.e(TAG, "Data ${it[1]}")
                if (it.size > 1)
// I don't know what it[1] is so I assume it is map<String, Any> since it was in an original issue.
                    callback((it[1] as Map<String, Any>).toObject<T, String, Any>())
            }.apply{
                [email protected](event, this)
                [email protected](event, this)
            }
        }
    }

when its usage now can be reduced to

 SomeClass().listen<Message>("someEvent", { message ->
// do some stuff with message
            })

This is not the fix to an original conversion issue from Map to Pojo. This is an architectural fix of the flawed methods.

Hope it helps.

Upvotes: 2

Related Questions