Reputation: 23
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
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
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.
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.
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