在 Android 与 JVM 生态的 JSON 解析方案中,Square 出品的 Moshi 凭借对 Kotlin 的良好适配、轻量高效的特性,成为很多项目替代 Gson 的首选。它既保留了简洁的 API 设计,又提供了编译期代码生成与反射两种解析模式,尤其适配 Kotlin 的空安全、data class 与默认参数特性,同时与 OkHttp、Retrofit 等 Square 技术栈无缝衔接。
本文将从环境配置、基础用法、进阶场景到踩坑指南,系统讲解 Moshi 在 Kotlin 项目中的完整使用方式。
如果想把JSON字符串转Kotlin或者Java可以使用下面的网站,支持添加Moshi,Gson等注解 devfy9.com/zh/json
一、环境准备:两种接入模式
Moshi 针对 Kotlin 提供了两种使用模式:运行时反射模式与 KSP 编译期代码生成模式。前者接入简单,后者性能更优、无反射开销,是生产环境的推荐方案。
1. 核心依赖
两种模式都需要引入 Moshi 核心库,当前稳定版本以 1.15.1 为例:
kotlin
// 模块级 build.gradle.kts
dependencies {
implementation("com.squareup.moshi:moshi:1.15.1")
}
2. 反射模式(快速接入)
引入 Kotlin 反射适配器后,无需额外注解即可直接解析 data class。缺点是依赖 kotlin-reflect,会增加应用包体积,且运行时反射存在固定性能损耗,适合小型项目或快速原型开发。
kotlin
dependencies {
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
}
3. KSP 代码生成模式(生产推荐)
通过 KSP 在编译期生成 JsonAdapter 类,完全避免运行时反射,解析速度更快,也无需依赖反射库,是官方推荐的生产级方案。
第一步:在项目根 build.gradle.kts 中添加 KSP 插件(版本需与 Kotlin 版本匹配)
kotlin
plugins {
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}
第二步:模块级引入 KSP 插件与 Moshi 代码生成依赖
kotlin
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.squareup.moshi:moshi:1.15.1")
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
}
二、基础使用:序列化与反序列化
1. 定义数据类
使用 KSP 模式时,需要给 data class 添加 @JsonClass(generateAdapter = true) 注解,通知编译器生成对应的解析适配器。
kotlin
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class User(
val id: Int,
val username: String,
val email: String?,
val age: Int = 18 // Kotlin 默认参数可正常生效
)
2. 初始化 Moshi 实例
Moshi 实例内部维护了适配器缓存,全局单例复用即可,避免重复创建带来的性能开销。
kotlin
val moshi: Moshi = Moshi.Builder()
// 反射模式必须添加;纯 KSP 模式可省略
// .add(KotlinJsonAdapterFactory())
.build()
如果项目中同时存在 KSP 生成适配器与反射解析的类,则必须添加 KotlinJsonAdapterFactory 作为兜底。
3. JSON 反序列化(字符串转对象)
通过 adapter() 获取对应类型的解析器,再调用 fromJson 完成解析:
kotlin
val json = """{"id":1,"username":"ZhangSan","email":"zhangsan@example.com"}"""
val userAdapter = moshi.adapter(User::class.java)
val user: User? = userAdapter.fromJson(json)
println(user?.username) // 输出 ZhangSan
println(user?.age) // 输出 18,字段缺失时默认参数生效
4. JSON 序列化(对象转字符串)
调用 toJson 即可将对象转为 JSON 字符串:
kotlin
val user = User(id = 2, username = "LiSi", email = null)
val json: String = userAdapter.toJson(user)
println(json)
// 输出:{"id":2,"username":"LiSi","email":null}
5. 字段名映射
当服务端返回字段命名风格与 Kotlin 驼峰规范不一致时,使用 @Json 注解指定字段映射关系:
kotlin
@JsonClass(generateAdapter = true)
data class Article(
val id: Int,
@Json(name = "article_title")
val articleTitle: String,
@Json(name = "create_time")
val createTime: Long
)
三、进阶场景:泛型、自定义与容错
1. 解析集合与泛型类型
受 Java 泛型擦除机制影响,无法直接通过 List<User>::class.java 获取集合类型,需要借助 Types.newParameterizedType 构造参数化类型:
kotlin
import com.squareup.moshi.Types
val userListJson = """
[{"id":1,"username":"A"},{"id":2,"username":"B"}]
""".trimIndent()
// 构造 List<User> 类型
val listType = Types.newParameterizedType(List::class.java, User::class.java)
val listAdapter = moshi.adapter<List<User>>(listType)
val userList: List<User>? = listAdapter.fromJson(userListJson)
2. 自定义类型适配器
对于日期、枚举、自定义类等特殊类型,可以通过继承 JsonAdapter 实现定制化的序列化与反序列化逻辑。以时间戳转 LocalDateTime 为例:
kotlin
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
class LocalDateTimeAdapter : JsonAdapter<LocalDateTime>() {
override fun fromJson(reader: JsonReader): LocalDateTime? {
val timestamp = reader.nextLong()
return Instant.ofEpochMilli(timestamp)
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
}
override fun toJson(writer: JsonWriter, value: LocalDateTime?) {
value?.let {
writer.value(it.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
} ?: writer.nullValue()
}
}
将自定义适配器注册到 Moshi 中,全局生效:
kotlin
val moshi = Moshi.Builder()
.add(LocalDateTimeAdapter())
.build()
3. 容错与严格模式
- 默认行为:JSON 中包含实体类未定义的字段时,Moshi 会自动忽略,不会抛出异常,兼容性更强。
- 严格模式:若需要在联调阶段校验接口字段,可开启未知字段报错:
kotlin
val moshi = Moshi.Builder()
.failOnUnknown() // 遇到未知字段直接抛出异常
.build()
四、Kotlin 生态适配:Retrofit 集成与空安全
1. 与 Retrofit 联合使用
Moshi 是 Retrofit 官方适配的解析器之一,引入对应转换器即可完成无缝接入,是 Android 网络请求场景的经典组合。
kotlin
dependencies {
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
}
构建 Retrofit 实例时传入 Moshi 转换器:
kotlin
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
2. Kotlin 空安全特性
Moshi 严格遵守 Kotlin 的空安全规则,这也是它相比 Gson 最核心的优势之一:
- 字段声明为非空类型时,若 JSON 中该字段为 null 或缺失,Moshi 会直接抛出解析异常,从源头避免隐性空指针。
- 字段声明为可空类型时,正常解析 null 值。
- 配合 Kotlin 默认参数,字段缺失时会自动使用默认值填充,不会触发空安全异常。
而 Gson 会通过反射绕过 Kotlin 空安全检查,给非空字段赋值 null,容易在运行期埋下难以排查的崩溃隐患。
五、常见踩坑与最佳实践
-
忘记添加 @JsonClass 注解 使用 KSP 模式时,若 data class 遗漏
@JsonClass(generateAdapter = true),Moshi 会回退到反射模式解析;若项目未引入反射库,则会直接抛出异常。 -
混淆配置 纯 KSP 代码生成模式下,不需要额外配置混淆规则;若使用反射模式,需要在 ProGuard 中保留数据类成员与 Kotlin 反射相关类,避免解析失败。
-
复杂泛型嵌套解析 对于
Map<String, List<User>>这类多层嵌套泛型,可通过Types.newParameterizedType嵌套构造类型:
kotlin
val mapType = Types.newParameterizedType(
Map::class.java,
String::class.java,
Types.newParameterizedType(List::class.java, User::class.java)
)
- 选型边界
- 纯 Kotlin Android 项目、Square 技术栈:优先 KSP 模式 Moshi,兼顾性能与空安全。
- KMM 跨平台项目:Moshi 仅支持 JVM,应选择 kotlinx.serialization。
- 后端 Spring 生态:Jackson 功能更完善、适配更广,优先选择。
总的来说,Moshi 是 Kotlin JVM/Android 场景下非常均衡的 JSON 解析方案,它既解决了 Gson 在 Kotlin 环境下的空安全隐患,又保持了轻量易用的特性,配合 KSP 代码生成可以在性能上达到最优。对于基于 Retrofit + OkHttp 的 Android 项目,Moshi 是同技术栈下的最优解之一。