If someone can, please explain why this TypeConverter works:
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
abstract class SetConverter <T>{
private val gson = Gson()
private val setType = object: TypeToken<Set<T>>(){}.type
@TypeConverter
fun toJson(set: Set<T>): String{
return gson.toJson(set, setType)
}
@TypeConverter
fun fromJson(json: String): Set<T>{
return gson.fromJson(json, setType)
}
}
In Entity I use SetConverter through this implementation:
class CalendarSetConverter: SetConverter<Calendar>()
And this works.
But a similar TypeConverter for the class does not work.
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
abstract class ClassConverter<T> {
private val gson = Gson()
private val type = object : TypeToken<T>(){}.type
@TypeConverter
fun toJson(value: T): String{
return gson.toJson(value, type)
}
@TypeConverter
open fun fromJson(json: String): T{
return gson.fromJson(json, type)
}
}
Implementation in Entity:
class CalendarConverter : ClassConverter<Calendar>()
And this implementation throw error in runtime while read Calendar from database:
Caused by: java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to java.util.Calendar
How to create universal generic TypeConverter for Room?
Actually your
CalendarSetConverteris most likely also not behaving as you intended, but due to type erasure you most likely have not noticed the issue yet. If you try to use the elements inside theSetreturned byCalendarSetConverteryou will also run into aClassCastException:The underlying issue for both of this is also type erasure. You should never create an
object : TypeToken<...>which captures a type parameter (e.g.T) unless that type parameter is reified. Otherwise at compile time the upper bound of the type parameter is used, orAnyif no explicit bound is specified. So what your code actually looks like is this:And Gson deserializes
Any(respectivelyjava.lang.Object) as aMap, more precisely as its internal classLinkedTreeMap, which causes theClassCastExceptionyou are experiencing. This does not apply to serialization (toJson) where Gson will use the runtime type when serializingAny.To fix this you will have to take the
TypeTokenas constructor parameter, e.g.:And then create the converter subclasses like this:
I don't know however if / how well this works with Room. Maybe this question provides better solutions to this.