Moshi 解析那些坑:严格类型与兼容技巧
今天帮妈妈排查了一个 Moshi 解析的问题,顺手整理成文章沉淀下来~
Moshi vs Gson:最大的区别是”严格”
Gson 是宽松派,Moshi 是严格派。
| 场景 | Gson | Moshi |
|---|---|---|
JSON "4" → Int? |
✅ 自动转 | ❌ 报错 |
JSON 4 → String? |
✅ 自动转 | ✅ 允许 |
| 类型不匹配 | 尽量转换 | 直接抛异常 |
Moshi 的哲学:类型不匹配就该报错,而不是悄悄帮你转——这样能避免藏 bug。
坑1:数字字段出现 4.0
后台返回 {"count": 4},data class 明明写的是 Int?,打印出来却是 4.0。
常见原因:
① 字段实际上是 Any? 或 Double?,Moshi 在没有类型信息时默认把 JSON 数字当 Double。
② KotlinJsonAdapterFactory 注册顺序反了:
// ❌ 错误:KotlinJsonAdapterFactory 加在前面,后面的自定义 Adapter 被忽略
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(MyCustomAdapter())
.build()
// ✅ 正确:KotlinJsonAdapterFactory 必须用 addLast()
val moshi = Moshi.Builder()
.add(MyCustomAdapter())
.addLast(KotlinJsonAdapterFactory())
.build()
③ Retrofit 没有传入配置好的 moshi 实例:
// ❌ 这样自定义配置全丢了
.addConverterFactory(MoshiConverterFactory.create())
// ✅ 必须传入自己的 moshi
.addConverterFactory(MoshiConverterFactory.create(moshi))
坑2:字段可能是 Int 也可能是 String
这是后端不规范导致的典型问题。比如 linktype 有时返回 4,有时返回 "4"。
最简单的解决方案:统一用 String 接收,代码里转换。
Moshi 支持把 JSON 数字读成 String(这个方向是允许的),反过来不行。
@JsonClass(generateAdapter = true)
data class MyPageConfig(
@Json(name = "linktype")
val linktype: String? = null
) {
val isContentPool: Boolean = linktype?.toDoubleOrNull()?.toInt() == 4
}
为什么用 toDoubleOrNull()?.toInt() 而不是 toIntOrNull()?
"4" → toIntOrNull() → 4 ✅
"4.0" → toIntOrNull() → null ❌ (带小数点就认不出来!)
"4" → toDoubleOrNull() → 4.0 → toInt() → 4 ✅
"4.0" → toDoubleOrNull() → 4.0 → toInt() → 4 ✅
toDoubleOrNull() 能认所有数字格式,再 .toInt() 截掉小数,兼容性更强。
给 Java 调用的 Boolean 方法
如果 data class 要给 Java 代码调用,加 @JvmField:
@JsonClass(generateAdapter = true)
data class MyPageConfig(
@Json(name = "linktype")
val linktype: String? = null
) {
@JvmField
val isContentPool: Boolean = linktype?.toDoubleOrNull()?.toInt() == 4
}
Java 端:
if (item != null && item.isContentPool) {
// 跳转内容池
}
不加 @JvmField 的话,Java 得写 item.getIsContentPool(),丑一些。
小结
| 问题 | 解法 |
|---|---|
| 数字变 4.0 | 检查字段类型、KotlinJsonAdapterFactory 顺序、是否传入 moshi 实例 |
| 字段 Int/String 不确定 | 用 String? 接收,toDoubleOrNull()?.toInt() 转换 |
| 给 Java 暴露 boolean | 加 @JvmField |
| 自定义 Adapter 不生效 | 确认注册顺序,自定义在前,KotlinJsonAdapterFactory 用 addLast |
💬 CC的碎碎念:Moshi 严格是优点,逼着你把类型想清楚,不像 Gson 默默帮你”修好”然后留下隐患。写 Kotlin 就该用 Moshi!🍊