Android Room中如何使用JSON_EXTRACT?Room是否支持该函数?
嘿,我来帮你梳理下这个问题的原因和解决办法~
为什么会报错?
你遇到的android.database.sqlite.SQLiteException: no such function: json_extract,核心原因是Android系统自带的SQLite版本不够。json_extract是SQLite 3.18.0才新增的JSON处理函数,而Android 7.0(API 24)及以上的系统才默认搭载这个版本的SQLite;如果你的App运行在API 23或更低版本的设备上,就会出现“找不到函数”的错误。
Room本身并没有限制使用JSON函数,它完全依赖底层系统提供的SQLite支持能力,所以不是Room不支持,是低版本系统不支持。
两种可行的解决方案
方法1:用Room TypeConverter解析JSON(推荐,全版本兼容)
这种方法不需要依赖SQLite的JSON函数,而是把JSON字符串转换成Java/Kotlin对象,直接操作对象属性来查询,兼容性拉满。
步骤走一遍:
- 先定义对应JSON结构的数据类,匹配你
products数组里的元素:
// 对应你JSON里的$[0].qty_case字段 data class Product( @SerializedName("qty_case") val qtyCase: Int )
- 写一个TypeConverter,实现JSON字符串和
List<Product>的互转(这里用Gson举例,Moshi等库也可以):
class ProductConverter { private val gson = Gson() @TypeConverter fun fromJson(jsonStr: String): List<Product> { // 先处理你原查询里的replace(tfo.products, '\','')逻辑 val cleanedJson = jsonStr.replace("\\", "") return gson.fromJson(cleanedJson, object : TypeToken<List<Product>>() {}.type) } @TypeConverter fun toJson(productList: List<Product>): String { return gson.toJson(productList) } }
- 在你的Room Database类上添加
@TypeConverters注解,让Room知道这个转换器:
@Database(entities = [Tfo::class], version = 1) @TypeConverters(ProductConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun tfoDao(): TfoDao }
- 修改DAO里的查询语句,直接访问转换后的对象属性就行:
@Query(""" SELECT SUM(products[0].qtyCase) as QtyCase FROM tfo WHERE strftime('%m', dateCreated) = strftime('%m', 'now', '0 months') AND armstrong2CustomersId = 'OPDB00140428AU' AND armstrong2TfoId = '9466c9d5-7d58-4d92-96e0-010662aa0055' """) fun getTotalQtyCase(): Int?
这里的products是你Tfo实体类里定义的List<Product>类型字段,Room会自动用转换器处理JSON的转换。
方法2:硬刚SQL层面的解决方案
如果你一定要用SQL里的JSON操作,可以选下面两种子方案:
子方案A:只支持高版本设备
直接保留原查询里的json_extract,但把App的最低SDK版本设为24:android { defaultConfig { minSdkVersion 24 } }这样就能保证运行的设备都支持这个函数。
子方案B:自定义JSON解析函数
要兼容低版本的话,可以给Room添加自定义函数来替代json_extract:
先在构建Room数据库的时候注册自定义函数:val db = Room.databaseBuilder(context, AppDatabase::class.java, "app_database") .addCallback(object : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // 注册自定义的json_extract函数 db.execSQL(""" CREATE FUNCTION json_extract(json TEXT, path TEXT) RETURNS TEXT LANGUAGE JAVA AS 'com.yourpackage.JsonUtils.extractJsonValue'; """) } }) .build()然后实现对应的静态方法来解析JSON:
object JsonUtils { @JvmStatic fun extractJsonValue(json: String?, path: String?): String? { if (json.isNullOrEmpty() || path.isNullOrEmpty()) return null // 先处理转义字符 val cleanedJson = json.replace("\\", "") val gson = Gson() val jsonElement = JsonParser.parseString(cleanedJson) // 简单解析路径,比如处理$[0].qty_case这种格式 val pathParts = path.removePrefix("$").split(".") var currentElement = jsonElement for (part in pathParts) { currentElement = when { part.matches(Regex("\\[\\d+\\]")) -> { // 数组索引 val index = part.substring(1, part.length - 1).toInt() currentElement.asJsonArray[index] } else -> { // 对象字段 currentElement.asJsonObject.get(part) } } } return currentElement.asString } }这个方法需要自己实现路径解析逻辑,适合简单场景,复杂路径可能需要更完善的处理。
对你原查询的小优化
原查询里的(select distinct replace(tfo.products, '\',''))其实可以简化,因为每个tfo记录的products都是唯一的,直接用replace(tfo.products, '\','')就行,没必要加select distinct。用TypeConverter方案的话,这个替换逻辑可以统一放在转换器里,更整洁。
内容的提问来源于stack exchange,提问作者Xiao King

