82

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.
Adam Varhegyi
  • 11,307
  • 33
  • 124
  • 222
j2emanue
  • 60,549
  • 65
  • 286
  • 456
  • do you also have a converter from `String` to `List`? I don't see it in your code. Room will need that as well to be able to read the data back. – yigit Jun 17 '17 at 02:52
  • I tried that but I'd did not help. Same error – j2emanue Jun 17 '17 at 04:18
  • 1
    TypeConverter for lists does work. If it's not working for you, you probably forget to add a method for the reverse conversion. `@TypeConverter public static List jsonToCountryLang(String json)` – Gak2 Feb 09 '18 at 20:57

14 Answers14

49

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.

hexicle
  • 2,121
  • 2
  • 24
  • 31
Jack Dalton
  • 3,536
  • 5
  • 23
  • 40
  • Jack, do you know how to store list of Enum? – murt Jun 22 '17 at 11:35
  • I'll have a look, post it as a question so that other people will easily be able to find the question + answer. Help out the community :) – Jack Dalton Jun 22 '17 at 12:09
  • I've posted my response – Jack Dalton Jun 22 '17 at 13:01
  • Recently I had problems with this approach combined with `@Embedded`. Room said it `Cannot figure out how to save this field into database`. Does anybody have any idea on why this could happen? – kkaun Sep 26 '18 at 15:22
39

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();
    }
}
Martin Zeitler
  • 1
  • 19
  • 155
  • 216
live-love
  • 48,840
  • 22
  • 240
  • 204
  • 6
    Note that I also had to add `@TypeConverters(DateConverter.class)` to the room database class as per Google's article: https://developer.android.com/training/data-storage/room/referencing-data – Evelyn Feb 10 '20 at 21:26
13

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);
    }
MorZa
  • 2,215
  • 18
  • 33
  • 3
    Cache `new Gson` in a field. It's a heavy object and takes long to create, you don't want to create a new instance every time the converter is called. The same goes for the type token. – Eugen Pechanec Mar 16 '19 at 20:37
  • this is the most valid answer – blackHawk May 10 '19 at 08:53
  • Yes indeed, this is the best answer here because it serializes/deserializes using Gson, and the underlying structure is of course Json. This is going to work with any object. I am fairly sure the accepted (and more upvoted) answer is going to produce some unexpected behavior if the object has a strong field that has commas in it. Also, what @EugenPechanec points out is important, but I wouldn't change the answer because it would complicate the answer a bit and detract from understanding the main concept here. – Thomas Carlisle May 17 '19 at 18:41
  • @EugenPechanec So how can we cache new Gson in a field? Can you provide some details regarding it? – Sumit Shukla Apr 24 '23 at 01:47
  • @AndroidDev It means the Gson object should be outside the method, as a class variable, and not inside the method. – MorZa Apr 24 '23 at 07:29
10

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

Abhijith Brumal
  • 1,652
  • 10
  • 14
7

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

@Embedded
private List<CrewListBean> crewList;
Liya
  • 568
  • 7
  • 28
  • Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.Listerror: Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.List – A_rmas Apr 20 '20 at 18:23
  • This can be a solution, see https://developer.android.com/training/data-storage/room/relationships#nested-objects. Currently I am trying to join two tables 1:1, because JSON has nested field. – CoolMind May 25 '20 at 13:22
6

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() {
sourav pandit
  • 8,647
  • 2
  • 19
  • 17
  • in my case, only first 2 methods are generated in java. https://stackoverflow.com/questions/76745360/multiple-typeconverters-with-room – Psijic Jul 22 '23 at 19:14
5

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

dferenc
  • 7,918
  • 12
  • 41
  • 49
Eduards Pozarickis
  • 219
  • 1
  • 3
  • 9
  • 4
    do we need any other configuration? I change `List` to `ArrayList` but still receive `error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private java.util.ArrayList notificationTypes; ^` – Linh Dec 17 '18 at 09:25
  • @PhanVanLinh we are using `Kotlin`, so with `kotlin.collections.ArrayList` it was fine for custom objects. For the `kotlin.String` there was no need to use `ArrayList`, it was fine with simple `kotlin.collections.List` – Eduards Pozarickis Dec 17 '18 at 14:29
  • I also still see the error even after using `kotlin.collections.ArrayList` on my custom object. – Adam Johns Oct 21 '21 at 15:05
4

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)
    }
Daniyal Javaid
  • 1,426
  • 2
  • 18
  • 32
3

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
    }
}
norbDEV
  • 4,795
  • 2
  • 37
  • 28
  • thanks for answer, what about adding this type while migrating into new room table version? having trouble how to define type of this Categories object in migration operation like this database.execSQL – Zafer Celaloglu Apr 21 '20 at 15:23
2

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)
Martin Zeitler
  • 1
  • 19
  • 155
  • 216
1

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

Braian Coronel
  • 22,105
  • 4
  • 57
  • 62
1

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

Hari Lee
  • 31
  • 3
0

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.

fawaad
  • 341
  • 6
  • 12
Aman Gupta - ΔMΔN
  • 2,971
  • 2
  • 19
  • 39
-2

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

adiga
  • 34,372
  • 9
  • 61
  • 83
Honey
  • 55
  • 1
  • 7