Android room persistent library - TypeConverter error of error: Cannot figure out how to save field to database"

Im not able to create a typeConverter in room due to an error. I seem to be following everything per the docs. I would like to convert a list to a json string. lets take a look at my entity:

      @Entity(tableName = TABLE_NAME)
public class CountryModel {


public static final String TABLE_NAME = "Countries";


@PrimaryKey
private int idCountry;
/* I WANT TO CONVERT THIS LIST TO A JSON STRING */
private List<CountryLang> countryLang = null;


public int getIdCountry() {
return idCountry;
}


public void setIdCountry(int idCountry) {
this.idCountry = idCountry;
}


public String getIsoCode() {
return isoCode;
}


public void setIsoCode(String isoCode) {
this.isoCode = isoCode;
}


public List<CountryLang> getCountryLang() {
return countryLang;
}


public void setCountryLang(List<CountryLang> countryLang) {
this.countryLang = countryLang;
}


}

The country_lang is what i would like to convert to a string json. So i created the following converter: Converters.java:

public class Converters {


@TypeConverter
public static String countryLangToJson(List<CountryLang> list) {


if(list == null)
return null;


CountryLang lang = list.get(0);


return list.isEmpty() ? null : new Gson().toJson(lang);
}}

then the problem is anywhere i put the @TypeConverters({Converters.class}) i keep getting an error. But officially this is where i have placed the annotation to get the typeConverter registered:

@Database(entities = {CountryModel.class}, version = 1 ,exportSchema = false)
@TypeConverters({Converters.class})
public abstract class MYDatabase extends RoomDatabase {
public abstract CountriesDao countriesDao();
}

The error i get is:

Error:(58, 31) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
98688 次浏览

This is a common problem I've seen since Room was announced. Room does not support the ability to store Lists directly, nor the ability to convert to/from Lists. It supports converting and storing POJO's.

In this case the solution is simple. Instead of storing a List<CountryLang> you want to store CountryLangs (note the 's')

I've done a quick example of a solution here :

public class CountryLangs {
private List<String> countryLangs;


public CountryLangs(List<String> countryLangs) {
this.countryLangs = countryLangs;
}


public List<String> getCountryLangs() {
return countryLangs;
}


public void setCountryLangs(List<String> countryLangs) {
this.countryLangs = countryLangs;
}
}

This POJO is an inversion of your previous object. It is an object that stores a list of languages. Instead of a list of objects that store your language.

public class LanguageConverter {
@TypeConverter
public CountryLangs storedStringToLanguages(String value) {
List<String> langs = Arrays.asList(value.split("\\s*,\\s*"));
return new CountryLangs(langs);
}


@TypeConverter
public String languagesToStoredString(CountryLangs cl) {
String value = "";


for (String lang :cl.getCountryLangs())
value += lang + ",";


return value;
}
}

This converter takes a list of strings and converts them into a comma seperated string to be stored in a single column. When it fetches the string from the SQLite db to convert back, it splits the list on commas, and populates the CountryLangs.

Insure to update your RoomDatabase version after making these changes.You have the rest of the configuration correct. Happy hunting with the rest of your Room persistence work.

Had same error: "Cannot figure out how to save this field into database" while trying to add a Date field. Had to add a converter class for it and add @TypeConverters annotation to field.

Example:

WordEntity.java

import androidx.room.TypeConverters;


@Entity
public class WordEntity {


@PrimaryKey(autoGenerate = true)
public int id;


private String name;


@TypeConverters(DateConverter.class)
private Date createDate;


...
}

DateConverter.java:

import androidx.room.TypeConverter;
import java.util.Date;


public class DateConverter {


@TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}


@TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}

@TypeConverter doesn't recognize List class, so you should use ArrayList instead, so you don't need additional wrapper for the list you want to persist.

You also have to create this TypeConverter which will convert your List to String,

@TypeConverter
public List<CountryLang> toCountryLangList(String countryLangString) {
if (countryLangString == null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<List<CountryLang>>() {}.getType();
List<CountryLang> countryLangList = gson.fromJson(countryLangString, type);
return countryLangList;
}

And for more info you can also check my another answer.

Kotlin example (not good but simple, TODO: json):

import android.arch.persistence.room.*


@Entity(tableName = "doctor")
data class DoctorEntity(


@PrimaryKey
@ColumnInfo(name = "id") val id: Long,


@ColumnInfo(name = "contactName") val contactName: String?,


@TypeConverters(CategoryConverter::class)
@ColumnInfo(name = "categories") val categories: Categories?,


@TypeConverters(CategoryConverter::class)
@ColumnInfo(name = "languages") val languages: Categories?
)


data class Categories(
val categories: ArrayList<Long> = ArrayList()
)


class CategoryConverter {


@TypeConverter
fun toCategories(value: String?): Categories {
if (value == null || value.isEmpty()) {
return Categories()
}


val list: List<String> = value.split(",")
val longList = ArrayList<Long>()
for (item in list) {
if (!item.isEmpty()) {
longList.add(item.toLong())
}
}
return Categories(longList)
}


@TypeConverter
fun toString(categories: Categories?): String {


var string = ""


if (categories == null) {
return string
}


categories.categories.forEach {
string += "$it,"
}
return string
}
}

I used the type converters described here (Article at Medium.com) and it worked:

@TypeConverter
public static List<MyObject> storedStringToMyObjects(String data) {
Gson gson = new Gson();
if (data == null) {
return Collections.emptyList();
}
Type listType = new TypeToken<List<MyObject>>() {}.getType();
return gson.fromJson(data, listType);
}


@TypeConverter
public static String myObjectsToStoredString(List<MyObject> myObjects) {
Gson gson = new Gson();
return gson.toJson(myObjects);
}

Had the same issue. Change the List to ArrayList. The list is an interface and room is unable to store it.

just annotate that object with @Embedded solved my issue . Like this

@Embedded
private List<CrewListBean> crewList;

If you want to make a custom class compatible (different from the supported ones) you must provide a bidirectional @TypeConverter converter which converts the custom one to one known by Room and vice versa.

For example, if we want to keep instances of LatLng:

Precondition: implementation("com.squareup.moshi:moshi-kotlin:1.9.2")

Converters.kt

@TypeConverter
fun stringToLatLng(input: String?): LatLng? =
input?.let { Moshi.Builder().build().adapter(LatLng::class.java).fromJson(it) }


@TypeConverter
fun latLngToString(input: LatLng): String? =
Moshi.Builder().build().adapter(LatLng::class.java).toJson(input)

Room already knows how to store a String.

With these converters, you can use your custom types in other queries, just as you would with primitive types

Location.kt

@Entity
data class Location(private val location: LatLng?)

GL

Source

Just in case if you need more clarity.

First create a converter general class like below.

class Converters {


@TypeConverter
fun fromGroupTaskMemberList(value: List<Comment>): String {
val gson = Gson()
val type = object : TypeToken<List<Comment>>() {}.type
return gson.toJson(value, type)
}


@TypeConverter
fun toGroupTaskMemberList(value: String): List<Comment> {
val gson = Gson()
val type = object : TypeToken<List<Comment>>() {}.type
return gson.fromJson(value, type)
}

}

Then add this converter in data base class just like,

@TypeConverters(Converters::class)

abstract class AppDatabase : RoomDatabase() {

I might be late to answer but.I have some simple solution for this i am sharing the TypeConverters call which handle some basice requirement

class RoomConverters {
//for date and time convertions
@TypeConverter
fun calendarToDateStamp(calendar: Calendar): Long = calendar.timeInMillis


@TypeConverter
fun dateStampToCalendar(value: Long): Calendar =
Calendar.getInstance().apply { timeInMillis = value }


//list of cutome object in your database
@TypeConverter
fun saveAddressList(listOfString: List<AddressDTO?>?): String? {
return Gson().toJson(listOfString)
}


@TypeConverter
fun getAddressList(listOfString: String?): List<AddressDTO?>? {
return Gson().fromJson(
listOfString,
object : TypeToken<List<String?>?>() {}.type
)
}


/*  for converting List<Double?>?  you can do same with other data type*/
@TypeConverter
fun saveDoubleList(listOfString: List<Double>): String? {
return Gson().toJson(listOfString)
}


@TypeConverter
fun getDoubleList(listOfString: List<Double>): List<Double> {
return Gson().fromJson(
listOfString.toString(),
object : TypeToken<List<Double?>?>() {}.type
)
}


// for converting the json object or String into Pojo or DTO class
@TypeConverter
fun toCurrentLocationDTO(value: String?): CurrentLocationDTO {
return  Gson().fromJson(
value,
object : TypeToken<CurrentLocationDTO?>() {}.type
)
}


@TypeConverter
fun fromCurrentLocationDTO(categories: CurrentLocationDTO?): String {
return Gson().toJson(categories)


}


}

you have to write own classes and parse in here after that add it to your AppDatabase class

@Database(
entities = [UserDTO::class],
version = 1, exportSchema = false
)
@TypeConverters(RoomConverters::class)
@Singleton
abstract class AppDatabase : RoomDatabase() {

Solutions given here are incomplete, once you complete the process given here in the accepted answer you need to add another annotation in you Entity class as well

@TypeConverters(Converter.class)
private List<String> brandId;

This should be put on the element which is causing the error in Room DB

happy coding

You can avoid object to string(json) type converter for all objects using only this type converter

@TypeConverter
fun objectToJson(value: Any?) = Gson().toJson(value)
 

I use this and has to only define converter for string(json) to object eg.

@TypeConverter
fun stringToPersonObject(string: String?): Person? {
return Gson().fromJson(string, Person::class.java)
}

One can organize back & forth @TypeConverter as @TypeConverters:

public class DateConverter {
@TypeConverter
public long from(Date value) {
return value.getTime();
}
@TypeConverter
public Date to(long value) {
return new Date(value);
}
}

And then apply them to fields with:

@TypeConverters(DateConverter.class)