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

AndroidAndroid Room

Android Problem Overview


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.

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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();
    }
}

Solution 3 - Android

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);
    }

Solution 4 - Android

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() {

Solution 5 - Android

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

@Embedded
private List<CrewListBean> crewList;

Solution 6 - Android

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() {

Solution 7 - Android

@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.

Solution 8 - Android

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
    }
}

Solution 9 - Android

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)
    }

Solution 10 - Android

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

Solution 11 - Android

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

Solution 12 - Android

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.

Solution 13 - Android

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)

Solution 14 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionj2emanueView Question on Stackoverflow
Solution 1 - AndroidJack DaltonView Answer on Stackoverflow
Solution 2 - Androidlive-loveView Answer on Stackoverflow
Solution 3 - AndroidMorZaView Answer on Stackoverflow
Solution 4 - AndroidAbhijith BrumalView Answer on Stackoverflow
Solution 5 - AndroidLiyaView Answer on Stackoverflow
Solution 6 - Androidsourav panditView Answer on Stackoverflow
Solution 7 - AndroidEduards PozarickisView Answer on Stackoverflow
Solution 8 - AndroidnorbDEVView Answer on Stackoverflow
Solution 9 - AndroidDaniyal JavaidView Answer on Stackoverflow
Solution 10 - AndroidBraian CoronelView Answer on Stackoverflow
Solution 11 - AndroidHari LeeView Answer on Stackoverflow
Solution 12 - AndroidAman Gupta - ΔMΔNView Answer on Stackoverflow
Solution 13 - AndroidMartin ZeitlerView Answer on Stackoverflow
Solution 14 - AndroidHoneyView Answer on Stackoverflow