I have a list of custom objects and the TypeConverters are not working as I expected.
Method threw 'com.google.gson.JsonSyntaxException' exception.
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
@Query("SELECT emaps FROM emaps_users WHERE userId = :userId LIMIT 1")
suspend fun getUserEmapsIds(userId : Int) : List<Emap>
Thats the query thats throwing the error.
Here my class:
ENTITY
@Entity(tableName = "emaps_users")
data class UserEmaps(
@PrimaryKey(autoGenerate = false)
val userId : Int,
@field:TypeConverters(ListEmapConverter::class)
val emaps : List<Emap>
)
@Entity(tableName = "eMaps")
data class Emap(
@PrimaryKey(autoGenerate = false)
override val id : Int = -1,
var nombreEMap : String = "",
var estado : Estado? = Estado.NONE,
private var listPois : List<Int> = mutableListOf(),
var isla: Isla? = Isla.NONE,
var publico : MutableSet<Publico> = mutableSetOf(),
var tematica : MutableSet<Tematica> = mutableSetOf(),
var dificultad : MutableSet<Dificultad> = mutableSetOf(),
var duracion : MutableSet<Duracion> = mutableSetOf(),
var descripcion : String = "",
var isCustom : Boolean = true,
var isPublic : Boolean = false
)
TYPECONVERTERS
@TypeConverter
fun fromEmapListToString(emaps: List<Emap>): String {
return GsonBuilder().create().toJson(emaps)
}
@TypeConverter
fun fromStringtoEmapList(emapsString: String): List<Emap> {
val listType = object : TypeToken<List<Emap>>(){}.type
return GsonBuilder().create().fromJson(emapsString, listType)
}
@TypeConverter
fun fromStringToEmap(value: String): Emap {
val listType: Type = object : TypeToken<Emap>() {}.type
return Gson().fromJson(value, listType)
}
@TypeConverter
fun fromEmapToString(list: Emap): String {
return Gson().toJson(list)
}
DATABASE
@Database(
entities = [
User::class,
Term::class,
Emap::class,
Page::class,
PageIndex::class,
Incidencia::class,
Emap.Poi::class,
UserEmaps::class,
CuadernoDeViaje::class
], version = 2, exportSchema = false
)
@TypeConverters(Converters::class)
abstract class CanaryDatabase : RoomDatabase()
I have tried to put the TypeConverter in several different places and nothing, nothing has worked for me, if someone knows what is failing it would be of great help.
This answer shows a working solution that you may wish to consider.
Instead of directly storing a List as a column the column is given a type that has a single field that is a List
i.e.
/* Added to simple solution */
data class EmapListClass(
val emapList: List<Emap>
)
This caters for simpler GSON/JSON handling the TypeConverters instead be:-
@TypeConverter
fun fromEMapListClassToJSONSTring(emapListClass: EmapListClass):String = Gson().toJson(emapListClass)
@TypeConverter
fun fromStringToEmapListClass(jsonString: String): EmapListClass = Gson().fromJson(jsonString,EmapListClass::class.java)
Demo
To demonstrate consider the following Entities that have been modified to make the demo far simpler (no reliance upon guessing the Types and Type Converters of all the other types not provided):-
@Entity(tableName = "emaps_users")
data class UserEmaps(
@PrimaryKey(autoGenerate = false)
val userId : Long?=null,
/*@field:TypeConverters(ListEmapConverter::class)*/
//val emaps : List<Emap> **** ALTERED for simple solution ****
val emaps: EmapListClass
)
@Entity(tableName = "eMaps")
data class Emap(
@PrimaryKey(autoGenerate = false)
/*override*/ val id : Int = -1,
var nombreEMap : String = "",
/* commented out for simplicity
var estado : Estado? = Estado.NONE,
private var listPois : List<Int> = mutableListOf(),
var isla: Isla? = Isla.NONE,
var publico : MutableSet<Publico> = mutableSetOf(),
var tematica : MutableSet<Tematica> = mutableSetOf(),
var dificultad : MutableSet<Dificultad> = mutableSetOf(),
var duracion : MutableSet<Duracion> = mutableSetOf(),
var descripcion : String = "",
var isCustom : Boolean = true,
var isPublic : Boolean = false
*/
)
An @Dao
annotated interface to cater for the demo:-
@Dao
interface AllDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(userEmaps: UserEmaps): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(emap: Emap): Long
@Query("SELECT emaps FROM emaps_users WHERE userId = :userId LIMIT 1")
/*suspend commented out to run on main thread for brevity */ fun getUserEmapsIds(userId : Int) : List<UserEmaps>
}
The following @Database
annotated abstract class, noting that for brevity of the demo, the main thread is used:-
@TypeConverters(ListEmapConverter::class)
@Database(entities = [Emap::class,UserEmaps::class], version = 1, exportSchema = false)
abstract class CanaryDatabase : RoomDatabase() {
abstract fun getAllDAOs(): AllDAOs
companion object {
private var instance: CanaryDatabase?=null
fun getInstance(context: Context): CanaryDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,CanaryDatabase::class.java,"canary.db")
.allowMainThreadQueries()
.build()
}
return instance as CanaryDatabase
}
}
}
Finally some application code that will insert some data and then extract it as per:-
class MainActivity : AppCompatActivity() {
lateinit var db: CanaryDatabase
lateinit var dao: AllDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = CanaryDatabase.getInstance(this)
dao = db.getAllDAOs()
val emaplist1 = ArrayList<Emap>()
emaplist1.add(Emap(1,"EM001"))
emaplist1.add(Emap(2,"EM002"))
emaplist1.add(Emap(3,"EM003"))
val elc: EmapListClass = EmapListClass(emaplist1)
for (el in emaplist1) {
dao.insert(el)
}
val emaplist2 = ArrayList<Emap>()
emaplist2.add(Emap(21,"EM021"))
emaplist2.add(Emap(22,"EM022"))
emaplist2.add(Emap(23,"EM023"))
for (el in emaplist2) {
dao.insert(el)
}
val um1 = dao.insert(UserEmaps(emaps = EmapListClass(emaplist1)))
val um2 = dao.insert(UserEmaps(emaps = EmapListClass(emaplist2)))
/* Retrieving the data from the database */
val sb = StringBuilder()
for (um in dao.getUserEmapsIds(um1.toInt())) {
sb.clear()
for (em in um.emaps.emapList) {
sb.append("\n\tEMAP ID is ${em.id} The NOMBREMAP is ${em.nombreEMap}")
}
Log.d("DBINFO","ID is ${um.userId} The EMAPLIST has ${um.emaps.emapList.size} emaps. They are:- ${sb}")
}
for (um in dao.getUserEmapsIds(um2.toInt())) {
sb.clear()
for (em in um.emaps.emapList) {
sb.append("\n\tEMAP ID is ${em.id} The NOMBREMAP is ${em.nombreEMap}")
}
Log.d("DBINFO","ID is ${um.userId} The EMAPLIST has ${um.emaps.emapList.size} emaps. They are:- ${sb}")
}
}
}
When run for the first time (the demo is only intended to run once). The output to the log includes (the relevant data):-
D/DBINFO: ID is null The EMAPLIST has 3 emaps. They are:-
EMAP ID is 1 The NOMBREMAP is EM001
EMAP ID is 2 The NOMBREMAP is EM002
EMAP ID is 3 The NOMBREMAP is EM003
D/DBINFO: ID is null The EMAPLIST has 3 emaps. They are:-
EMAP ID is 21 The NOMBREMAP is EM021
EMAP ID is 22 The NOMBREMAP is EM022
EMAP ID is 23 The NOMBREMAP is EM023
This is expected.
Using App Inspection
then:-
and :-
Note the GSON library used was com.google.code.gson:gson:2.11.0
(there are other GSOn libraries and they may have different features/capabilities). Some may be better suited to handling Lists