安卓房间持久化库 - TypeConverter 错误: 错误: 无法确定如何将字段保存到数据库

84

由于错误,我无法在Room中创建一个typeConverter。 我似乎按照文档的要求做了一切。 我想将列表转换为JSON字符串。 让我们来看看我的实体:

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

}

country_lang 是我希望将其转换为字符串 JSON 的内容。因此,我创建了以下转换器:

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

然后问题出在我把@TypeConverters({Converters.class})放在哪里都会出错。但是官方文档说这是注册typeConverter的正确位置:

那么我的问题是任何地方我放置@TypeConverters({Converters.class})都会报错。但是官方文档中指定了正确的注解位置来注册TypeConverter:

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

我遇到的错误是:

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

你们是否也有从StringList<CountryLang>的转换器?我在你们的代码中没有看到它。Room也需要它来读取数据。 - yigit
我尝试过了,但没有帮助。仍然是同样的错误。 - j2emanue
1
TypeConverter对于列表确实有效。如果它对您不起作用,则可能忘记添加反向转换方法。 @TypeConverter public static List<CountryLang> jsonToCountryLang(String json) - Gak2
14个回答

50

自从 Room 被宣布以来,我看到了这个常见的问题。Room 不支持直接存储列表,也不支持转换为/从列表。它支持转换和存储 POJO。

在这种情况下,解决方案很简单。你可以存储 CountryLangs(注意 's'),而不是存储一个 List<CountryLang>

我在这里给出了一个快速解决方案的示例:

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

这个POJO是你以前对象的反转。它是一个存储语言列表的对象,而不是存储你的语言的对象列表。

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

这个转换器接收一组字符串,将它们转换为逗号分隔的字符串并存储在单个列中。从SQLite数据库检索字符串以进行转换时,它会在逗号上拆分列表,并填充CountryLangs。

请确保在进行这些更改后更新您的RoomDatabase版本。您已经正确配置了其余部分。祝你在持久化工作的其余部分好运。


1
杰克,你知道如何存储枚举列表吗? - murt
我会看一下,把它发布为一个问题,这样其他人就可以方便地找到问题和答案。帮助社区 :) - Jack Dalton
我已经发布了我的回复。 - Jack Dalton
最近我在使用@Embedded时遇到了问题。Room报错说它“无法确定如何将此字段保存到数据库中”。有人知道为什么会出现这种情况吗? - kkaun

39

尝试添加日期字段时,出现错误信息:“无法将此字段保存到数据库中”。需要添加转换器类并在字段上添加@TypeConverters注释。

示例:

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:

日期转换器.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();
    }
}

6
请注意,根据Google的文章(https://developer.android.com/training/data-storage/room/referencing-data),我还必须在Room数据库类中添加`@TypeConverters(DateConverter.class)`。 - Evelyn

14

我使用了这里(Medium.com上的文章)描述的类型转换器,并且它起作用了:

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

4
在一个字段中缓存new Gson。这是一个重量级对象,创建需要很长时间,你不希望每次调用转换器时都创建一个新实例。对于类型标记也是同样的道理。 - Eugen Pechanec
这是最正确的答案。 - blackHawk
确实,这是最好的答案,因为它使用Gson进行序列化/反序列化,底层结构当然是Json。这将适用于任何对象。我相当确定被接受的(并且更受欢迎的)答案如果对象具有带逗号的强字段,则会产生一些意外行为。此外,@EugenPechanec指出的内容很重要,但我不会改变答案,因为这会使答案变得复杂,并减少理解主要概念的能力。 - Thomas Carlisle
@EugenPechanec 那么我们如何在字段中缓存新的Gson呢?您能提供一些详细信息吗? - Sumit Shukla
@AndroidDev 这意味着Gson对象应该在方法外作为类变量,而不是在方法内部。 - MorZa

11

如果您需要更多的澄清,请参考以下内容。

首先创建一个通用转换器类,如下所示。

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

}

然后像这样在数据类中添加此转换器:

@TypeConverters(Converters::class)

抽象类 AppDatabase 继承自 RoomDatabase 类:

abstract class AppDatabase : RoomDatabase() {


7
只需在该对象上注释@Embedded,即可解决我的问题。像这样:
@Embedded
private List<CrewListBean> crewList;

实体和POJO必须有可用的公共构造函数。您可以拥有一个空构造函数或一个构造函数,其参数与字段(按名称和类型)匹配。- java.util.List错误:实体和POJO必须有可用的公共构造函数。您可以拥有一个空构造函数或一个构造函数,其参数与字段(按名称和类型)匹配。- java.util.List - A_rmas
这可能是一个解决方案,请参见https://developer.android.com/training/data-storage/room/relationships#nested-objects。目前我正在尝试连接两个 1:1 的表,因为 JSON 有嵌套字段。 - CoolMind

6

虽然我的回答可能有点晚,但我有一些简单的解决方案。我分享一下TypeConverters调用,它可以处理一些基本需求。

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)

}

}

你需要编写自己的类并在此解析,之后将其添加到你的AppDatabase类中。
@Database(
        entities = [UserDTO::class],
        version = 1, exportSchema = false
         ) 
@TypeConverters(RoomConverters::class)
@Singleton
abstract class AppDatabase : RoomDatabase() {

在我的情况下,只有前两种方法在Java中生成。 https://stackoverflow.com/questions/76745360/multiple-typeconverters-with-room - Psijic

5

@TypeConverter 无法识别 List 类型,因此建议您使用 ArrayList 来代替,这样您就不需要为想要持久化的列表添加额外的包装。


4
我们需要进行其他配置吗?我将“List”更改为“ArrayList”,但仍然收到错误提示:“无法确定如何将此字段保存到数据库中。您可以考虑为其添加类型转换器。” private java.util.ArrayList<java.lang.String> notificationTypes; ^ - Linh
@PhanVanLinh 我们正在使用 Kotlin,所以对于自定义对象,使用 kotlin.collections.ArrayList 是可以的。对于 kotlin.String,不需要使用 ArrayList,只需使用简单的 kotlin.collections.List 即可。 - Eduards Pozarickis
即使我在自定义对象上使用了 kotlin.collections.ArrayList,我仍然看到错误。 - Adam Johns

4

您可以使用此类型转换器避免为所有对象进行对象到字符串(json)类型转换

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

我使用这个方法,只需要为字符串(json)定义一个转换器即可将其转换为对象。

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

3

可以将前后的@TypeConverter组织成@TypeConverters

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

然后使用以下方式将它们应用于字段:

@TypeConverters(DateConverter.class)

3

Kotlin示例(不是很好但简单,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
    }
}

谢谢您的回答,那么在迁移到新的房间表版本时添加这种类型怎么样?我遇到了麻烦,不知道如何在迁移操作中定义此类别对象的类型,例如database.execSQL。 - Zafer Celaloglu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接