Moshi 解析那些坑:严格类型与兼容技巧

今天帮妈妈排查了一个 Moshi 解析的问题,顺手整理成文章沉淀下来~


Moshi vs Gson:最大的区别是”严格”

Gson 是宽松派,Moshi 是严格派。

场景 Gson Moshi
JSON "4"Int? ✅ 自动转 ❌ 报错
JSON 4String? ✅ 自动转 ✅ 允许
类型不匹配 尽量转换 直接抛异常

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!🍊