Open chaitanya-anand opened 2 months ago
If possible, could you please create a small self-contained example or demo project? Otherwise it is difficult to reproduce this or to find the cause. For example maybe there is some non-type-safe code involved which causes the incorrect deserialized class.
Could be related to / same issue as #419 or #1708 though, for example here a custom List
subtype MyList
also causes a ClassCastException
:
interface MyList<T> extends List<T> {}
public static void main(String[] args) {
var listType = new TypeToken<MyList<String>>() {};
MyList<String> list = new Gson().fromJson("[]", listType);
}
Same happening for RealmDictionary field com.example.refactor.models.dbmodels.Form.collaborators has type io.realm.kotlin.types.RealmDictionary, got java.util.LinkedHashMap
When changing it to RealmList error converts to has type io.realm.kotlin.types.RealmList, got java.util.ArrayList
Hi sorry for the late reply.
I have created a demo android project to showcase the error:
MainActivity
package com.jubl.realmgsontest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.jubl.realmgsontest.databinding.ActivityMainBinding
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btn1.setOnClickListener {
val jsonString = serializeJson()
binding.text1.text = jsonString
deserializeJson(jsonString)
}
}
private fun serializeJson(): String {
val modelB: RealmList<ModelB> = realmListOf(
ModelB().apply { internalName = "Alice"
internalId = "B1"},
ModelB().apply { internalName = "Bob"
internalId = "B2"},
ModelB().apply { internalName = "Cat"
internalId = "B3"}
)
val modelA: ModelA = ModelA().apply {
id = "A1"
name = "ModelAName"
listCourses = realmListOf("CS","IT","ECE")
listModelB = modelB
}
val gson = Gson()
val toJson = gson.toJson(modelA)
println(toJson)
return toJson
}
private fun deserializeJson(json: String) {
val gson = GsonBuilder().serializeNulls().create()
val model = gson.fromJson(json, ModelA::class.java)
println(model)
}
}
with the following model:
ModelA and ModelB:
package com.jubl.realmgsontest
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.EmbeddedRealmObject
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
class ModelA: RealmObject {
@PrimaryKey
var id: String = ""
var name: String = ""
var listCourses: RealmList<String> = realmListOf()
var listModelB : RealmList<ModelB> = realmListOf()
}
class ModelB: EmbeddedRealmObject {
var internalName: String = ""
var internalId: String = ""
}
Gson breaks with the following stack trace when the deserializeJson()
function runs:
FATAL EXCEPTION: main
Process: com.jubl.realmgsontest, PID: 19640
java.lang.IllegalArgumentException: field com.jubl.realmgsontest.ModelA.listCourses has type io.realm.kotlin.types.RealmList, got java.util.ArrayList
at java.lang.reflect.Field.set(Native Method)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField(ReflectiveTypeAdapterFactory.java:433)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:393)
at com.google.gson.Gson.fromJson(Gson.java:1227)
at com.google.gson.Gson.fromJson(Gson.java:1137)
at com.google.gson.Gson.fromJson(Gson.java:1047)
at com.google.gson.Gson.fromJson(Gson.java:982)
at com.jubl.realmgsontest.MainActivity.deserializeJson(MainActivity.kt:54)
at com.jubl.realmgsontest.MainActivity.onCreate$lambda$0(MainActivity.kt:24)
at com.jubl.realmgsontest.MainActivity.$r8$lambda$XNagGdFbnmIcx7WnNlWYg20JNeM(Unknown Source:0)
at com.jubl.realmgsontest.MainActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7659)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1218)
at android.view.View.performClickInternal(View.java:7636)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:30155)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8176)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
and as @sonuindori commented, the same error occurs when using RealmDictionary too.
For context here are the related dependencies I am using:
id 'io.realm.kotlin' version '1.16.0' apply false
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
Let me know if you need any more info.
Thanks a lot for this detailed information!
I think even if this specific IllegalArgumentException
was fixed, Gson would still fail with a different expected exception: The issue is that you are trying to deserialize an interface, and Gson cannot know which specific implementation of that interface you actually want to deserialize.
To solve this, you need to register a custom InstanceCreator
which creates an instance of RealmList
, so that Gson can use that for deserializing the values. Alternatively you can also register a TypeAdapter
or JsonDeserializer
, but that is probably not needed here.
I am not familiar with the realm-kotlin library, but it seems you can probably use realmListOf()
and realmDictionaryOf()
within those InstanceCreator
s. Please let me know if something regarding this is unclear.
Note however that as mentioned in Gson's README, support for Kotlin and other JVM languages is limited. It seems realm-kotlin provides a io.realm.kotlin.serializers
package. Maybe that would be more helpful?
In case my suggestion to use an InstanceCreator
does not help, I will try your code snippet.
I tried using InstanceCreator
, however I am still getting the same error. Am I doing something wrong?
My InstanceCreator
package com.jubl.realmgsontest
import com.google.gson.InstanceCreator
import io.realm.kotlin.ext.realmListOf
import java.lang.reflect.Type
class ModelACreator: InstanceCreator<ModelA> {
override fun createInstance(type: Type?): ModelA {
val model: ModelA = ModelA().apply {
listCourses = realmListOf()
listModelB = realmListOf()
}
// model.listCourses = realmListOf<String>()
// model.listModelB = realmListOf<ModelB>()
return model
}
}
Changed the deserializeJson()
function to:
private fun deserializeJson(json: String) {
val gson = GsonBuilder()
.registerTypeAdapter(ModelA::class.java, ModelACreator())
.create()
val model = gson.fromJson(json, ModelA::class.java)
println(model)
}
From what I could understand, this error is occurring because Gson is deserialising the listCourses
into a Java or Kotlin ArrayList
or List
and then setting the realmList
in ModelA
like this: modelA.listCourses = deserialisedList<String>
.
However this is not supported by realmList. (Basically you cannot do something like this: val rl: RealmList<String> = listOf("Apple", "Mango")
.
However we can do this:
val rl: RealmList<String> = realmListOf()
rl.addAll(deserialisedList)
So if somehow we can tell Gson to use the addAll
function, then I think we can solve this issue.
What I meant was registering InstanceCreator
s for RealmList
and RealmDictionary
(since those are the types which Gson cannot deserialize on its own):
val gson = GsonBuilder()
.registerTypeAdapter(RealmList::class.java, InstanceCreator {
realmListOf<Any>()
})
.registerTypeAdapter(RealmDictionary::class.java, InstanceCreator {
realmDictionaryOf<Any>()
})
.create()
(Same probably also for RealmSet
if you use that too.)
At least in my local tests that seems to solve the issue.
Thank you for your help. This worked perfectly.
Gson version
2.10.1 AND 2.11.0
Java / Android version
Android version 13
Used tools
Stack Trace
Description
When deserialising a JSON into an object containing RealmList Gson is unable to populate it correctly and throws the following error. In fact the same error occurs in case of RealmDictionary too.
Expected behavior
The deserialised JSON should correctly populate the any list type to RealmList
Actual behaviour
IllegalArgumentException is thrown