当前位置: 首页 > news >正文

Moshi:Kotlin 原生 JSON 库的序列化与反序列化实战指南

1. 从 Gson 到 Moshi:为什么我们需要一个“对 Kotlin 友好的” JSON 库?

如果你是一个 Android 开发者,或者正在用 Kotlin 写后端服务,处理 JSON 数据绝对是日常操作。过去几年,甚至十几年,Gson 和 Jackson 几乎是 Java 生态里处理 JSON 的默认选择。它们很强大,也很成熟,但当你把项目语言切换到 Kotlin 后,事情就开始变得有点“别扭”了。

我最早用 Gson 时,觉得它真方便,一个fromJson就能把字符串变成对象。但后来在 Kotlin 项目里,我遇到了几个典型问题:我的数据类(Data Class)明明定义了非空(non-null)属性,但 Gson 反序列化时,如果 JSON 里这个字段缺失了,它不会报错,而是默默地把这个属性设置成null。这直接破坏了 Kotlin 的空安全契约,导致后续代码里可能出现意料之外的NullPointerException,让 Kotlin 最大的卖点之一形同虚设。另一个问题是默认值,Kotlin 允许你在主构造函数参数里直接给默认值,比如val name: String = “Unknown”,但 Gson 在反序列化时,如果字段缺失,它并不会使用这个默认值,而是会调用无参构造函数(如果存在)或者用其他方式初始化,结果往往不是你想的那样。

这就是“对 Kotlin 不友好”的典型表现。这些库在设计时,核心假设是围绕 Java 的反射和空指针语义,它们不理解、也不尊重 Kotlin 的语言特性,比如空安全、默认参数、不可变数据类等。

于是,Moshi 出现了。它由 Square 公司出品(对,就是做 OkHttp 和 Retrofit 的那家公司)。Moshi 的核心理念之一就是“一等公民式的 Kotlin 支持”。它从底层就为 Kotlin 设计,能够理解 Kotlin 的类型系统。例如,它会严格检查空安全,如果 JSON 中缺失了一个在 Kotlin 中声明为非空的字段,Moshi 会直接抛出JsonDataException,而不是给你一个null。对于有默认值的参数,如果 JSON 缺失,它会聪明地使用你定义的默认值。这种设计哲学,让 Moshi 和 Kotlin 的协作变得无比丝滑。

所以,当你的项目以 Kotlin 为主,尤其是大量使用数据类和非空类型时,Moshi 几乎是一个必然的选择。它解决的不仅仅是“能用”的问题,更是“用得安心”、“符合语言习惯”的问题。接下来,我们就深入看看,怎么把这个好用的工具真正用起来。

2. 环境搭建与基础依赖:从引入到第一个序列化

万事开头难,但 Moshi 的开头相当简单。我们以 Android 项目为例,如果你用的是 Gradle Kotlin DSL(.kts文件),在app模块的build.gradle.kts文件中添加依赖:

dependencies { // Moshi 核心库 implementation("com.squareup.moshi:moshi:1.15.0") // Moshi 的 Kotlin 代码生成支持(强烈推荐) kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.0") // 或者,如果你不想用 kapt,也可以用反射方式(性能稍差) // implementation("com.squareup.moshi:moshi-kotlin:1.15.0") }

这里有个关键选择:moshi-kotlin-codegen还是moshi-kotlin?我强烈推荐使用 Codegen(代码生成)的方式。它的原理是在编译期(kapt阶段)为你的每个需要序列化的 Kotlin 类生成一个对应的JsonAdapter。这样做的好处是:

  1. 零运行时反射:性能最好,因为所有适配逻辑都是编译时生成的普通代码。
  2. Proguard/R8 友好:不需要额外配置混淆规则来保留反射需要的类、方法名。
  3. 编译期错误检查:如果你的类无法被 Moshi 适配(比如有不支持的属性类型),会在编译时就报错,而不是等到运行时才崩溃。

moshi-kotlin是纯反射实现,它使用 Kotlin 反射在运行时去探查类的结构。虽然用起来更“自动”(连@JsonClass注解都可以省了),但会有性能损耗,并且需要配置混淆规则。对于新项目,无脑选 Codegen 就对了。

添加完依赖,同步项目。现在,我们来定义一个简单的数据类,并用 Moshi 处理它。

import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class User( val id: Long, val name: String, val email: String? = null, // 可空类型,且有默认值 null val isActive: Boolean = true // 非空类型,有默认值 true )

注意@JsonClass(generateAdapter = true)这个注解。这就是告诉 Moshi 的注解处理器:“请为这个类生成一个JsonAdapter”。这是使用 Codegen 方式必须的一步。

现在,让我们写一段最简单的序列化(对象转JSON字符串)和反序列化(JSON字符串转对象)的代码:

import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory fun main() { // 1. 构建 Moshi 实例 val moshi = Moshi.Builder() .addLast(KotlinJsonAdapterFactory()) // 添加 Kotlin 支持,处理泛型、密封类等 .build() // 2. 获取 User 类的 JsonAdapter // 如果你用了 Codegen,这里会使用生成的适配器,而不是反射。 // 但为了通用性,我们通过 moshi.adapter<T>() 来获取,它会自动找到最优的适配器。 val userAdapter = moshi.adapter<User>() // 3. 创建一个 User 对象 val user = User(id = 1L, name = “张三”) // email 使用默认值 null, isActive 使用默认值 true // 4. 序列化(toJson) val jsonString = userAdapter.toJson(user) println(“序列化结果:$jsonString”) // 输出类似:{“id”:1,“name”:“张三”,“isActive”:true} // 注意:email 字段因为值为 null,默认被省略了。这是 Moshi 的默认行为。 // 5. 反序列化(fromJson) val inputJson = “”“{“id”: 2, “name”: “李四”}”“” val parsedUser = userAdapter.fromJson(inputJson) println(“反序列化结果:$parsedUser”) // 输出:User(id=2, name=李四, email=null, isActive=true) // 看到了吗?JSON 里没有 email 和 isActive,但反序列化后的对象使用了我们定义的默认值! }

这段代码虽然简单,但已经揭示了 Moshi 的几个核心优点:

  • 空安全User.nameString(非空),如果 JSON 中name字段是null或者缺失,fromJson会直接抛出异常,而不是给你一个nullname
  • 默认值生效emailisActive都成功使用了数据类中定义的默认值。
  • 简洁的 APIadapter<T>()toJson()fromJson(),非常直观。

注意KotlinJsonAdapterFactoryaddLast方法很重要。Moshi 的适配器查找是链式的,addLast意味着这个工厂会最后被查询。这样,对于有自定义适配器或 Codegen 生成适配器的类,会优先使用它们;对于没有的(比如List<User>这样的泛型类型),才会 fallback 到这个 Kotlin 反射工厂来处理。这是推荐的配置方式。

3. 核心功能实战:处理字段映射、泛型与复杂结构

基础用法会了,但真实业务中的数据可没这么规整。API返回的JSON字段名可能不符合Kotlin的命名规范(比如用蛇形命名user_name),或者我们需要处理一些特殊类型(如日期、枚举),以及嵌套对象和集合。别担心,Moshi 都有优雅的解决方案。

3.1 自定义字段名与@Json注解

后端接口经常返回蛇形命名(snake_case)的字段,而 Kotlin 属性通常用驼峰命名(camelCase)。Moshi 提供了@Json注解来轻松映射。

@JsonClass(generateAdapter = true) data class ApiResponse( @Json(name = “user_id”) // 将 JSON 中的 “user_id” 映射到 Kotlin 属性 `userId` val userId: Long, @Json(name = “created_at”) val createdAt: String, // 日期先按字符串处理,后面我们再处理日期类型 val data: UserData // 嵌套对象 ) @JsonClass(generateAdapter = true) data class UserData( val items: List<String> ) fun main() { val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val adapter = moshi.adapter<ApiResponse>() val json = “”“ { “user_id”: 12345, “created_at”: “2023-10-27T10:30:00Z”, “data”: { “items”: [“apple”, “banana”] } } ”“”.trimIndent() val response = adapter.fromJson(json) println(response?.userId) // 输出:12345 // 即使 JSON 是 user_id,我们在代码里也能用 userId 访问,非常干净。 }

3.2 处理日期时间

JSON 标准没有日期类型,通常用 ISO 8601 格式的字符串表示。Moshi 本身不提供日期适配器,但我们可以轻松集成java.time(Android API 26+ 或通过脱糖支持)或kotlinx.datetime库。这里以集成java.time.Instant为例,我们需要一个自定义的JsonAdapter

首先,添加一个对moshi-adapters的依赖(这个模块包含了一些常用类型的适配器,但未必有你要的):

implementation(“com.squareup.moshi:moshi-adapters:1.15.0”)

但为了展示自定义适配器,我们手动为Instant写一个:

import com.squareup.moshi.* import java.time.Instant import java.time.format.DateTimeFormatter class InstantAdapter { @ToJson fun toJson(instant: Instant): String { return DateTimeFormatter.ISO_INSTANT.format(instant) } @FromJson fun fromJson(string: String): Instant { return Instant.from(DateTimeFormatter.ISO_INSTANT.parse(string)) } } // 使用它 @JsonClass(generateAdapter = true) data class Event( val name: String, @Json(name = “happened_at”) // 仍然可以用 @Json 注解映射字段名 val happenedAt: Instant ) fun main() { val moshi = Moshi.Builder() .add(InstantAdapter()) // 注册我们的自定义适配器 .addLast(KotlinJsonAdapterFactory()) .build() val eventAdapter = moshi.adapter<Event>() val event = Event(“Product Launch”, Instant.now()) val json = eventAdapter.toJson(event) println(json) // {“name”:“Product Launch”,“happened_at”:“2023-10-27T10:30:00Z”} val parsedEvent = eventAdapter.fromJson(json) println(parsedEvent?.happenedAt) // 输出一个 Instant 对象 }

@ToJson@FromJson注解让创建自定义适配器变得非常简单。你只需要在一个类里定义这两个方法,然后将这个类实例添加到Moshi.Builder中。Moshi 会自动识别方法的参数和返回类型,将其绑定到对应的 Kotlin/Java 类型上。

3.3 处理泛型集合与多态类型(密封类)

这是 JSON 解析中两个稍微高级但非常常见的场景。

泛型集合:得益于KotlinJsonAdapterFactory或 Codegen,Moshi 能很好地处理List<T>Map<String, T>这样的泛型集合。你只需要获取对应泛型类型的 Adapter 即可。

val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() // 获取 List<User> 的适配器 val listAdapter: JsonAdapter<List<User>> = moshi.adapter() val usersJson = “”“[{“id”:1,“name”:“A”}, {“id”:2,“name”:“B”}]”“” val userList = listAdapter.fromJson(usersJson)

多态类型(密封类):当 JSON 中某个字段可能是几种不同类型之一时(比如,一个消息通知的content字段,可能是文本、图片或链接),Kotlin 的密封类(Sealed Class)是完美的建模工具。Moshi 通过JsonAdapter工厂和@JsonClass注解来支持。

假设我们有一个消息内容:

@JsonClass(generateAdapter = true) sealed class MessageContent { @JsonClass(generateAdapter = true) data class Text(val text: String) : MessageContent() @JsonClass(generateAdapter = true) data class Image(val url: String, val caption: String?) : MessageContent() @JsonClass(generateAdapter = true) data class Link(val url: String, val title: String) : MessageContent() } @JsonClass(generateAdapter = true) data class Message( val id: Long, val type: String, // 我们可以用这个字段来判断类型 val content: MessageContent )

现在问题来了:Moshi 怎么知道一段 JSON 该对应TextImage还是Link呢?我们需要一个自定义的JsonAdapter,根据某个判别字段(这里是type)来决策。

class MessageContentAdapter { @FromJson fun fromJson(reader: JsonReader, delegate: JsonAdapter<MessageContent>): MessageContent? { // 这里需要一些技巧。一种常见模式是先将 JSON 读成一个 Map 或 JsonElement,判断后再解析。 // 更优雅的方式是使用 Moshi 的 `PolymorphicJsonAdapterFactory`(在 moshi-adapters 中)。 // 但为了理解原理,我们手动实现一下。 // 注意:手动实现较复杂,实际项目建议用 PolymorphicJsonAdapterFactory。 // 这里仅展示概念。 return delegate.fromJson(reader) // 简单起见,假设 delegate 能处理。实际需要更复杂的逻辑。 } @ToJson fun toJson(writer: JsonWriter, value: MessageContent?, delegate: JsonAdapter<MessageContent>) { delegate.toJson(writer, value) } }

实际上,对于这种多态情况,更推荐使用moshi-adapters模块中的PolymorphicJsonAdapterFactory,它能极大简化这项工作:

import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory val messageContentAdapterFactory = PolymorphicJsonAdapterFactory.of(MessageContent::class.java, “type”) .withSubtype(MessageContent.Text::class.java, “text”) .withSubtype(MessageContent.Image::class.java, “image”) .withSubtype(MessageContent.Link::class.java, “link”) val moshi = Moshi.Builder() .add(messageContentAdapterFactory) .addLast(KotlinJsonAdapterFactory()) .build() // 现在 Moshi 就能自动根据 JSON 中的 “type” 字段的值(“text”, “image”, “link”)来实例化正确的子类了。

4. 与 Retrofit 集成:打造类型安全的网络层

Moshi 和 Retrofit 同出自 Square,它们的集成是天作之合,能让你用最 Kotlin 的方式处理网络请求和响应。

假设我们有一个简单的用户 API:

import retrofit2.http.GET import retrofit2.http.Path interface UserApiService { @GET(“users/{id}”) suspend fun getUser(@Path(“id”) id: Long): ApiResponse<User> // 假设 ApiResponse 是一个包装类 @GET(“users”) suspend fun getUsers(): ApiResponse<List<User>> }

在创建 Retrofit 实例时,将 Moshi 的转换工厂提供给它:

import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory val moshi = Moshi.Builder() .addLast(KotlinJsonAdapterFactory()) .build() val retrofit = Retrofit.Builder() .baseUrl(“https://api.example.com/”) .addConverterFactory(MoshiConverterFactory.create(moshi)) // 关键在这里 .build() val userApi = retrofit.create(UserApiService::class.java)

就这样!Retrofit 会自动使用 Moshi 来解析接口返回的 JSON 数据,转换成你定义的 Kotlin 数据类。整个过程是类型安全的,并且完美支持 Kotlin 的空安全和默认值。

一个实战中的坑:有时候 API 返回的 JSON 最外层是一个固定的包装结构,比如{“code”: 0, “message”: “success”, “data”: {...}}。我们不想在每个 API 接口的返回类型里都写ApiResponse<X>,更希望直接拿到data里的内容,并且能统一处理code非 0 的错误情况。这需要用到 Retrofit 的CallAdapter。我们可以自定义一个,或者使用一些现成的库(如retrofit2.adapter.resultResult类型)。核心思想是,在 Moshi 解析完 JSON 到ApiResponse<T>后,由CallAdapter检查code,如果成功则提取data并返回,如果失败则抛出包含message的异常。这属于网络层架构设计,篇幅所限不展开,但知道 Moshi 能无缝融入这个流程很重要。

5. 性能调优与问题排查:让 Moshi 飞得更稳

选择 Codegen 已经是最大的性能优化。但还有一些细节值得注意。

1. 重用 Moshi 实例和 AdapterMoshi 实例和 JsonAdapter 实例都是线程安全的,且构建成本较高。绝对不要在每次序列化/反序列化时都新建一个。应该在应用生命周期内(如通过依赖注入、单例或全局变量)创建并重用它们。

// 不好的做法 fun parseUserBad(json: String): User? { val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() // 每次新建! return moshi.adapter<User>().fromJson(json) } // 好的做法 object MoshiHelper { val moshi: Moshi by lazy { Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() } inline fun <reified T> adapter(): JsonAdapter<T> = moshi.adapter() } fun parseUserGood(json: String): User? { return MoshiHelper.adapter<User>().fromJson(json) // 重用 Adapter }

2. 注意 Proguard/R8 混淆如果你使用了 Codegen(moshi-kotlin-codegen),通常不需要额外的混淆规则,因为生成的代码是普通的类和方法。但如果你使用了moshi-kotlin(反射方式),或者集成了某些使用反射的自定义适配器,则需要在proguard-rules.pro中添加规则来保留相关的类名、方法名。对于 Codegen,基本可以忽略这一步。

3. 常见异常与排查

  • JsonDataException: Required value ‘name’ missing at $:这通常是因为 JSON 中缺失了 Kotlin 类中声明为非空(且无默认值)的字段。检查你的数据模型是否和 API 契约完全匹配。有时后端可能在某些情况下不返回某个字段,这时你需要将其在 Kotlin 中改为可空类型(String?)或提供合理的默认值。
  • JsonDataException: Expected a string but was NUMBER at path $.id:类型不匹配。例如,JSON 中id是数字,但你的 Kotlin 属性是String仔细核对字段类型
  • 使用 Codegen 时编译报错:Moshi doesn‘t know how to handle...:这可能是因为你的类中有 Moshi 无法自动处理的自定义类型。你需要为这个类型提供一个自定义的JsonAdapter,并通过@JsonClass(generateAdapter = false)告诉 Moshi 不要为这个类生成适配器,或者将你的自定义适配器添加到 Moshi 实例中。
  • 反序列化后属性全是默认值(null 或 false):这很可能是因为字段名映射错误。检查@Json(name = “...” )注解是否正确,或者属性名是否与 JSON 键名完全匹配(注意大小写)。可以用moshi.adapter<YourClass>().indent(“ “).toJson(yourObj)打印出 Moshi 期望的 JSON 格式,与实际的 API 响应对比。

4. 使用JsonReaderJsonWriter进行低级控制绝大多数情况下,你不需要直接操作JsonReaderJsonWriter。但如果你需要处理非常不规则、动态的 JSON,或者要实现极其定制化的解析逻辑(比如流式解析巨大的 JSON 文件),它们就派上用场了。你可以通过自定义JsonAdapter并重写fromJson(reader: JsonReader)toJson(writer: JsonWriter, value: T?)方法来获得完全的控制权。这属于高级用法,在需要性能极限优化或处理特殊格式时再考虑。

6. 进阶技巧:适配器组合、懒加载与 Kotlin 特性深挖

当你对 Moshi 的基础用法驾轻就熟后,下面这些技巧能让你的代码更加健壮和优雅。

1. 适配器组合(Adapter Composition)这是 Moshi 一个非常强大的特性。假设你有一个Settings类,里面包含一个Map<String, Any>用来存储动态配置。Any类型对 Moshi 来说太模糊了。你可以创建一个组合适配器,根据 Map 里值的实际类型来动态处理。

class SettingsMapAdapter { private val stringAdapter = Moshi.Builder().build().adapter<String>() private val intAdapter = Moshi.Builder().build().adapter<Int>() private val boolAdapter = Moshi.Builder().build().adapter<Boolean>() @FromJson fun fromJson(reader: JsonReader): Map<String, Any> { // 手动读取 JSON Object,根据值的类型调用不同的 adapter val map = mutableMapOf<String, Any>() reader.beginObject() while (reader.hasNext()) { val key = reader.nextName() when (reader.peek()) { JsonReader.Token.STRING -> map[key] = stringAdapter.fromJson(reader)!! JsonReader.Token.NUMBER -> map[key] = intAdapter.fromJson(reader)!! JsonReader.Token.BOOLEAN -> map[key] = boolAdapter.fromJson(reader)!! else -> reader.skipValue() // 忽略不支持的类型 } } reader.endObject() return map } @ToJson fun toJson(writer: JsonWriter, value: Map<String, Any>?) { // 类似地,根据值的类型调用不同的 adapter 写入 writer.beginObject() value?.forEach { (key, anyValue) -> writer.name(key) when (anyValue) { is String -> stringAdapter.toJson(writer, anyValue) is Int -> intAdapter.toJson(writer, anyValue) is Boolean -> boolAdapter.toJson(writer, anyValue) else -> writer.nullValue() // 或抛出异常 } } writer.endObject() } }

然后把这个适配器注册给Map<String, Any>类型。这展示了如何将简单的适配器组合起来处理复杂场景。

2. 懒加载(Lazy)属性的处理Kotlin 的by lazy是一种常见的属性委托,用于延迟初始化。但 Moshi 在反序列化时,会直接调用主构造函数来创建对象,by lazy的初始化块不会被执行(因为属性可能已经被构造函数参数赋值了)。如果你有一个属性,其值需要从其他属性计算得来,并且希望它被序列化/反序列化,更好的模式是:

  • 不序列化计算属性:将其声明为普通函数(fun calculatedValue(): String)或不在构造函数中的val(这样它就不会是数据类的一部分,默认不会被 Moshi 序列化)。
  • 需要序列化:将其包含在主构造函数中,即使它可以从其他字段推导。或者,使用自定义适配器在序列化时计算它,反序列化时忽略或从 JSON 中读取。

通常,计算属性不应该参与序列化/反序列化,因为它不是“状态”,而是状态的衍生品。

3. 使用@Transient注解忽略字段如果你有一个属性不需要参与序列化和反序列化(比如内存缓存、临时状态),可以用@Transient注解标记它。Moshi 会完全忽略这个字段。

@JsonClass(generateAdapter = true) data class User( val id: Long, val name: String, @Transient // 这个字段不会被序列化或反序列化 var cachedAvatar: Bitmap? = null )

4. 处理默认值策略默认情况下,Moshi 在序列化时会跳过值为null的字段(toJson时)。如果你希望显式输出null,可以在构建JsonAdapter时配置:

val adapter = moshi.adapter<User>().serializeNulls() val jsonWithNull = adapter.toJson(User(1, “Bob”, null)) // 输出包含 “email”: null

同样,反序列化时,如果 JSON 中有null,对于非空类型会抛异常,对于可空类型会赋值为null。这个行为是符合 Kotlin 空安全预期的。

7. 测试策略:确保你的 JSON 解析坚如磐石

对于核心的数据模型和 JSON 适配逻辑,编写测试至关重要。Moshi 的纯函数特性(输入 JSON,输出对象,或反之)让它非常易于测试。

1. 单元测试数据类解析使用 JUnit 和 Moshi,你可以轻松测试各种边界情况。

import com.google.common.truth.Truth.assertThat import org.junit.Test class UserParsingTest { private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() private val adapter = moshi.adapter<User>() @Test fun `parse complete user json`() { val json = “”“{“id”: 1, “name”: “Alice”, “email”: “alice@example.com”}”“” val user = adapter.fromJson(json) assertThat(user).isEqualTo(User(id = 1L, name = “Alice”, email = “alice@example.com”)) } @Test fun `parse user json without email uses default null`() { val json = “”“{“id”: 1, “name”: “Alice”}”“” val user = adapter.fromJson(json) assertThat(user).isEqualTo(User(id = 1L, name = “Alice”, email = null)) // email 是默认的 null } @Test(expected = JsonDataException::class) // 期望抛出异常 fun `parse user json without name should fail`() { val json = “”“{“id”: 1}”“” // 缺少非空字段 name adapter.fromJson(json) } @Test fun `serialize user with null email omits field`() { val user = User(id = 1L, name = “Alice”, email = null) val json = adapter.toJson(user) // 检查 json 不包含 email 字段 assertThat(json).doesNotContain(“email”) } }

2. 测试自定义适配器为你写的每一个自定义JsonAdapter(比如上面的InstantAdapter)编写测试,覆盖正常的序列化/反序列化,以及异常输入(如格式错误的日期字符串)。

3. 集成测试与 Retrofit使用 MockWebServer(OkHttp 的测试组件)可以模拟网络响应,并测试从 HTTP 请求到最终 Kotlin 对象的完整链条。这能确保你的 API 接口定义、Moshi 转换器和数据模型协同工作正常。

import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before import org.junit.Test import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory class UserApiTest { private lateinit var server: MockWebServer private lateinit var api: UserApiService @Before fun setup() { server = MockWebServer() server.start() val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val retrofit = Retrofit.Builder() .baseUrl(server.url(“/”)) .addConverterFactory(MoshiConverterFactory.create(moshi)) .build() api = retrofit.create(UserApiService::class.java) } @After fun teardown() { server.shutdown() } @Test fun `getUser returns parsed object`() = runBlocking { // 1. 准备模拟响应 val mockResponse = “”“ { “code”: 0, “message”: “success”, “data”: { “id”: 100, “name”: “Test User” } } ”“”.trimIndent() server.enqueue(MockResponse().setBody(mockResponse).setResponseCode(200)) // 2. 执行调用 val response = api.getUser(100) // 3. 验证结果 assertThat(response.code).isEqualTo(0) assertThat(response.data?.id).isEqualTo(100) assertThat(response.data?.name).isEqualTo(“Test User”) } }

通过分层测试,你可以自信地重构数据类或适配器逻辑,而不用担心破坏现有的 JSON 解析功能。

从最初的依赖引入,到基础的对象映射,再到处理复杂的多态类型、与网络库集成,最后到性能优化和完备的测试,Moshi 提供了一套完整、高效且“Kotlin 原生”的 JSON 处理方案。它强迫你思考数据模型的可空性,利用 Kotlin 的语言特性写出更安全的代码。迁移到 Moshi 可能需要一些前期投入(尤其是修改旧的数据类),但带来的类型安全性和开发体验的提升,绝对是值得的。下次启动新的 Kotlin 项目时,不妨直接选择 Moshi 作为你的 JSON 库,体验一下这种“友好”的感觉。

http://www.jsqmd.com/news/1381497/

相关文章:

  • 中兴B860AV2.1-T 3.0机顶盒线刷纯净当贝桌面固件完整教程
  • 有“〖深基X...〗”标识的题目**
  • Windows内核漏洞利用实战:从池溢出到权限提升的完整攻防解析
  • 国产化技术栈迁移实战:从X86到ARM的SpringBoot应用适配指南
  • 5种光标样式+3种颜色方案:打造你的专属Kitty终端光标体验
  • 办理公证认证需要哪些材料?全套申办资料详解 - luffy+2
  • iOS限制密码终极恢复指南:3步找回被遗忘的屏幕时间密码
  • 手撕 Function Calling:大模型怎么“调用工具“
  • 2026年云测平台对比:商业化与开源方案选型对照
  • 如何构建稳定可靠的Minecraft服务器:Pumpkin错误处理与日志监控完整指南
  • 家用全屋中央阻垢器哪个牌子好除水垢水碱无盐软水机耀龙泉品牌好用 - 精彩城市
  • 162、飞控中的最优控制:动态规划与HJB方程
  • 独立产品何时不该用 LLM:规则能解决的事别交给概率
  • notepad++ 快速跳转到某一行
  • 二十四
  • 3分钟上手:用Buzz打造你的个人离线语音转文字工作站
  • 图解书在妇科手术学习中的核心价值:从解剖基础到手术思维构建
  • 从“文字接龙”到办事助手:一文看懂 LLM、Tool、MCP 与 Agent
  • 2026纯种比熊犬舍选购** 新手选犬指南 - Full19
  • Python量化分析:手把手教你计算股票前复权与后复权价格
  • IIS 网站访问三大经典错误深度解析:端口访问失败、Web 服务扩展锁定、MIME 映射阻止完整解决方案 - 代码非世界
  • 终端网络分析利器Termshark:Wireshark的TUI替代方案
  • 大麦抢票终极指南:告别手速焦虑的完整自动化解决方案
  • SystemView移植实战:可视化调试FreeRTOS任务与中断交互
  • OpenClaw Skills安装指南与常见问题解决
  • Cocos Creator跨平台游戏开发:从入门到精通的终极指南
  • 基于规则引擎与有限状态机的轻量级语义解析实战
  • 小鼠标网站建设:从零基础到精通,普通人的数字化突围指南
  • 网上办理公证和线下办理有什么区别?优劣对比分明 - luffy+2
  • 终极指南:3步掌握ncmdump,轻松解密网易云NCM加密音乐