1. 简介
Retrofit 是一个类型安全的 HTTP 客户端,广泛用于 Java 和 Kotlin 开发中,其核心思想是将 REST API 抽象为接口。在实际开发中,我们常常需要将枚举(Enum)序列化为对应的字符串值,以确保应用与后端 API 之间能够正确地通信。
在本教程中,我们将探讨如何在 Kotlin 中使用 Retrofit 实现枚举的序列化和反序列化。
2. 创建枚举类
首先,我们定义一个表示订单状态的简单枚举:
enum class OrderStatus {
PENDING,
COMPLETED,
CANCELLED
}
接着,我们创建一个依赖于 OrderStatus
的数据类 Order
:
data class Order(val id: String, val status: OrderStatus)
最后,定义一个用于处理请求的接口 OrderService
:
interface OrderService {
@GET("orders/{id}")
suspend fun getOrder(@Path("id") orderId: String): Order
@POST("orders")
suspend fun createOrder(@Body order: Order): Order
}
3. Retrofit 的转换器机制
Retrofit 通过 Converter 接口实现数据的序列化和反序列化。它负责将原始的 JSON 或 XML 数据转换为 Kotlin 对象,反之亦然。Retrofit 支持多种转换器库,例如 Gson、Moshi 和 Jackson。
这意味着我们可以根据项目需求灵活选择 JSON 序列化库,只需在构建 Retrofit 实例时添加对应的 ConverterFactory
即可。
3.1. 使用 Gson 作为转换器
Gson 是 Google 提供的流行 JSON 序列化库,Retrofit 提供了 GsonConverterFactory
用于集成:
val gson: Gson = GsonBuilder().create()
val gsonRetrofit: Retrofit = Retrofit.Builder()
.baseUrl("http://localhost:8080")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
3.2. 使用 Moshi 作为转换器
Moshi 是由 Square 开发的轻量级 JSON 解析库,Retrofit 也提供了官方支持:
val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val moshiRetrofit: Retrofit = Retrofit.Builder()
.baseUrl("http://localhost:8080")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
3.3. 使用 Jackson 作为转换器
Jackson 是功能强大的 JSON 处理库,适合需要复杂序列化配置的场景:
val objectMapper: ObjectMapper = jacksonObjectMapper()
val jacksonRetrofit: Retrofit = Retrofit.Builder()
.baseUrl("http://localhost:8080")
.addConverterFactory(JacksonConverterFactory.create(objectMapper))
.build()
4. 枚举的序列化处理
上述三种转换器都对枚举有默认支持。默认情况下,它们会使用枚举的 name()
方法将其转换为字符串形式,例如 OrderStatus.PENDING
会被序列化为 "PENDING"
。
我们可以使用 WireMock 来模拟 HTTP 请求,验证 Retrofit 是否能正确地序列化和反序列化包含枚举的数据对象。
4.1. WireMock 初始化
在 JUnit 测试中使用 WireMock 模拟服务器行为:
class WireMockTest {
@Rule
@JvmField
val wireMockRule = WireMockRule(8080)
@BeforeEach
fun setup() {
if(wireMockRule.isRunning) return
wireMockRule.start()
}
@AfterEach
fun tearDown() {
wireMockRule.stop()
}
}
4.2. Gson 序列化测试
测试 Gson 默认的枚举序列化行为:
@Test
fun `test Gson default serialization`() = runBlocking {
val service = gsonRetrofit.create(OrderService::class.java)
val order = Order("1", OrderStatus.PENDING)
wireMockRule.stubFor(
WireMock.post(WireMock.urlEqualTo("/orders"))
.willReturn(WireMock.aResponse()
.withBody("""{"id":"1","status":"PENDING"}""")
.withStatus(200)
)
)
val response = service.createOrder(order)
assertEquals(order, response)
}
✅ 该测试验证了 Retrofit 使用 Gson 时能正确地将 OrderStatus.PENDING
转换为 "PENDING"
。
4.3. Moshi 序列化测试
测试 Moshi 的默认序列化行为:
@Test
fun `test Moshi default serialization`() = runBlocking {
val service = moshiRetrofit.create(OrderService::class.java)
val order = Order("1", OrderStatus.PENDING)
wireMockRule.stubFor(
WireMock.post(WireMock.urlEqualTo("/orders"))
.willReturn(WireMock.aResponse()
.withBody("""{"id":"1","status":"PENDING"}""")
.withStatus(200)
)
)
val response = service.createOrder(order)
assertEquals(order, response)
}
✅ Moshi 同样使用 name()
方法进行枚举序列化,行为与 Gson 一致。
4.4. Jackson 序列化测试
测试 Jackson 的默认行为:
@Test
fun `test Jackson default serialization`() = runBlocking {
val service = jacksonRetrofit.create(OrderService::class.java)
val order = Order("1", OrderStatus.PENDING)
wireMockRule.stubFor(
WireMock.post(WireMock.urlEqualTo("/orders"))
.willReturn(WireMock.aResponse()
.withBody("""{"id":"1","status":"PENDING"}""")
.withStatus(200)
)
)
val response = service.createOrder(order)
assertEquals(order, response)
}
✅ Jackson 也默认使用 name()
方法,所有测试均通过。
⚠️ 结论: Retrofit 中的 Gson、Moshi 和 Jackson 默认都会使用枚举的 name()
方法进行序列化和反序列化。
5. 总结
本文介绍了如何在 Kotlin 中使用 Retrofit 序列化枚举类型,并通过 WireMock 模拟测试验证了 Gson、Moshi 和 Jackson 的默认行为。它们都使用枚举的 name()
方法进行转换,这在大多数情况下已经足够使用。
如果你有自定义的序列化需求,例如希望将枚举序列化为特定的字符串(如数据库字段名、别名等),则需要通过自定义适配器或注解实现,这部分内容我们将在后续文章中展开。
完整代码示例可在 GitHub 上查看。