Moshi 是 Square 开发的现代 JSON 库,专为 Kotlin 和 Android 设计,充分考虑了 Gson 的局限性。它与 Kotlin 的 null 安全性完全兼容,在编译阶段生成代码,不使用反射,从而提高了性能和可靠性。根据 Square Moshi, 2024 的数据,Moshi 提供可预测的序列化,并支持任何数据类型的自定义适配器。
要点
Moshi 是用于 JVM、Android 和 Kotlin Multiplatform 的 JSON 库,由 Square(OkHttp 和 Retrofit 的创建者)开发。与 Gson 不同,Moshi 不依赖于反射 — 适配器通过 @JsonClass(generateAdapter = true) 注解在编译阶段生成。这使得 Moshi 在使用 Kotlin 特定构造时更快、更安全、更可预测。
Moshi 与其前辈的主要区别在于摒弃了反射。反射允许 Gson 无需准备即可与任何类一起工作,但代价是初始化缓慢、编译器无法优化以及运行时错误风险。Moshi 需要显式指定类进行代码生成,但作为回报,它提供了手写代码的速度和编译阶段的完全类型安全。
// 在 build.gradle 中连接 Moshi
dependencies {
implementation "com.squareup.moshi:moshi:1.15.0"
implementation "com.squareup.moshi:moshi-kotlin:1.15.0"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.15.0"
}
// 带有代码生成的简单模型
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_id")
val id: Int,
val name: String,
val email: String,
val avatar: String? = null
)
// 使用
val moshi = Moshi.Builder()
.build()
val jsonAdapter = moshi.adapter(User::class.java)
要开始使用 Moshi,需要在 build.gradle 中添加依赖项并注解模型。Moshi.Builder 作为入口点:通过它添加标准类型的内置适配器、自定义适配器,并配置库的行为。Moshi 开箱即用地支持 Date、Enum、Collection 和 Map 的适配器,但对于 Kotlin 类,需要 moshi-kotlin 模块。与 Gson 不同,Moshi 默认情况下不为 Kotlin 类使用反射 — 为此需要连接 KotlinJsonAdapterFactory,当未应用代码生成或类未使用 @JsonClass 注解时,它作为后备选项。这种方法保证了开发人员为每个特定类显式选择代码生成的性能与反射的灵活性。
通过 Builder 构建 Moshi 后,开发人员获取 Moshi 实例并为所需类请求适配器。JsonAdapter 是核心对象,通过 toJson() 执行序列化,通过 fromJson() 执行反序列化。如果类使用 @JsonClass(generateAdapter = true) 注解,Moshi 会自动使用生成的适配器,否则应用反射型 KotlinJsonAdapterFactory 作为后备选项。这种方法将代码生成的速度与反射机制的灵活性相结合,适用于任何规模和复杂程度的项目。Moshi 既适合小型应用,也适合拥有数百个数据模型的大型企业项目。
// 使用 KotlinJsonAdapterFactory 配置 Moshi
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(LocalDateAdapter())
.build()
// 使用适配器
val adapter = moshi.adapter(User::class.java)
// 序列化
val user = User(1, "Alice", "alice@test.com")
val json = adapter.toJson(user)
// 反序列化
val jsonString = """{"user_id":2,"name":"Bob","email":"bob@test.com"}"""
val parsedUser = adapter.fromJson(jsonString)
// 处理列表
val listAdapter = moshi.adapter(
Types.newParameterizedType(
List::class.java,
User::class.java
)
)
Moshi 使用注解来配置序列化和支持自定义类型。@Json(name = "...") 设置字段的 JSON 键。@Transient 将字段排除在序列化之外。@JsonClass(generateAdapter = true) 启用代码生成。对于自定义逻辑,Moshi 提供了 @ToJson 和 @FromJson 注解,可以放在单独的适配器类中。
@Json 注解替代了 Gson 的 @SerializedName,工作原理类似:kotlinName 字段与 JSON 键 “kotlin_name” 关联。对于 Moshi 默认无法序列化的类型(例如 LocalDate),开发人员创建一个带有 @ToJson 和 @FromJson 方法的类。适配器通过 Moshi.Builder.add() 注册,并全局或针对特定类型应用。Moshi 支持 sealed class 和通过 @JsonClass 显式指定鉴别器的多态序列化,允许在 JSON 中处理类型层次结构而无需手动检查字段。在反序列化时,Moshi 默认忽略 JSON 中的未知键,这确保了在服务器端添加新字段时无需修改客户端代码即可向后兼容。调试时可以通过 failOnUnknown 启用严格模式,在检测到未知键时抛出异常。
// LocalDate 的自定义适配器
class LocalDateAdapter {
@ToJson
fun toJson(date: LocalDate): String {
return date.format(DateTimeFormatter.ISO_LOCAL_DATE)
}
@FromJson
fun fromJson(dateString: String): LocalDate {
return LocalDate.parse(dateString)
}
}
// 带有 Moshi 注解的模型
@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "event_id")
val id: Int,
@Json(name = "event_date")
val date: LocalDate,
@Transient
val localCache: String? = null
)
// 注册适配器
val moshi = Moshi.Builder()
.add(LocalDateAdapter())
.add(KotlinJsonAdapterFactory())
.build()
比较 Moshi 和 Gson 是为 Android 项目选择 JSON 库时的常见问题。Moshi 凭借代码生成、null 安全性和速度在现代 Kotlin 开发中胜出。Gson 对于 Java 项目、遗留代码和需要最小配置的场景仍然适用。差异在大数据量和复杂模型中变得明显。
性能测试表明,带有代码生成的 Moshi 在序列化和反序列化操作中比 Gson 快 2-5 倍。Moshi 的关键优势在于正确处理 Kotlin 的 null 安全性:如果 JSON 中缺少字段,而模型中将该字段声明为 non-null 且没有默认值,Moshi 会在反序列化阶段抛出异常,防止隐藏的错误。
| 特性 | Gson | Moshi |
|---|---|---|
| 机制 | 反射 | 代码生成 / 反射 |
| Null safety | 不考虑 | 完全支持 Kotlin |
| 速度 | 中等 | 高 |
| 默认值 | 不支持 | 支持 |
| Kotlin Multiplatform | 否 | 是 |
| 库大小 | ~240 Kb | ~150 Kb |
Moshi 和 Gson 之间的选择取决于项目的上下文。新的 Kotlin 项目因类型安全性和性能而受益于 Moshi。Gson 仍然是支持 Java 代码、动态 JSON 结构或连接简单性比速度更重要的场景下的合理选择。对于 Kotlin Multiplatform,Moshi 是两个选项中唯一支持该平台的。
从 Gson 迁移到 Moshi 时,主要变化涉及注解和适配器。Gson 的 @SerializedName 替换为 @Json(name = "..."),自定义的 JsonSerializer/JsonDeserializer 替换为 @ToJson/@FromJson 对。对于具有默认值和可空字段的模型,Moshi 的行为更可预测:如果 JSON 中缺少没有默认值的 non-null 字段,Moshi 会抛出 JsonDataException,防止隐藏的 NPE。通过 MoshiConverterFactory 与 Retrofit 的集成只需一个依赖项,无需更改网络层架构。通过 ProGuard 或 R8 进行混淆时,需要添加保留 @JsonClass 注解类和生成适配器的规则,否则序列化将在发布版本中崩溃。总的来说,在性能和类型安全性重要的新 Kotlin 项目中,从 Gson 迁移到 Moshi 是合理的。
// 序列化比较:Gson vs Moshi
data class Sample(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
// Gson:通过反射工作
val gson = Gson()
val fromGson = gson.fromJson("""{"name":"test"}""",
Sample::class.java)
// count = 0 (default),但不检查 null 安全性
// Moshi:需要适配器,null 安全性是显式的
@JsonClass(generateAdapter = true)
data class SampleMoshi(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
常见问题
Moshi 是 Square 为 Kotlin 和 Android 提供的 JSON 库,使用代码生成代替反射。它提供高性能、正确处理的 Kotlin null 安全性以及与 Kotlin Multiplatform 的兼容性。
Moshi 在速度(由于代码生成快 2-5 倍)、安全性(考虑 Kotlin null 注解)和大小(小约 90 Kb)方面优于 Gson。Moshi 还支持 Kotlin Multiplatform 和 data class 中的默认值。
@JsonClass(generateAdapter = true) 指示 Moshi 在编译阶段为该类生成适配器。生成的适配器直接执行序列化,无需反射,从而提供最大性能。
创建一个类,其中的方法使用 @ToJson(序列化)和 @FromJson(反序列化)注解。通过 Moshi.Builder.add() 注册实例。Moshi 会在处理相应类型时自动找到并应用该适配器。
是的,Moshi 从 1.13.0 版本开始支持 Kotlin Multiplatform。这使其成为 KMP 项目唯一流行的 JSON 解决方案,允许在所有目标平台上使用通用的序列化代码。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。