使用 Kotlin 序列化反序列化 API 响应时如何省略包装类

问题描述 投票:0回答:1

我使用 KTOR 作为我的 CMP 应用程序的 Http 客户端。我想知道在从 API 获取数据时是否可以省略包装类并将它们直接加载到 DTO 中。

以下是我的设置:

API响应:

"data": {
    "id": 11002,
    "name": "name",
    "category": "category"
}

包装类:

@Serializable
data class SearchResponse(
    val data: Dto
)

最后,DTO 的结构与响应完全相同:

@Serializable
data class Dto(
    val id: String,
    val name: String,
    val category: String?
)

每当我在没有包装类的情况下进行调用时,KTOR 都会返回错误。当包含包装类时,一切正常。有没有办法省略包装类,直接使用DTO?

错误:

 io.ktor.serialization.JsonConvertException: Illegal input: Fields [id, name, category] are required for type with serial name 'com.example.dto', but they were missing at path: $
kotlin kotlin-multiplatform ktor kotlinx.serialization ktor-client
1个回答
1
投票

您使用包装类的 API 调用可以正常工作,因为响应的形式与您的

SearchResponse
类的标准 JSON 序列化形式相匹配。1

如果您为

Dto
编写
KSerializer
并将相同的 JSON 反序列化为
Dto
,则可以直接使用
Dto
类。

实现此目的的一种技术是使用 代理序列化器模式,尽管它比平常更复杂一点,因为要做到这一点,我们希望为

Dto
使用两个序列化器:Ktor 将使用的用户定义的序列化器,以及常规生成的用于使用代理对象进行序列化的对象(现在移至
DtoDeserializer.SearchResponse
)。为此,我们使用新的 feature,当在
Dto
上使用自定义序列化器时,它会保留生成的序列化器,而这种方法又是通过以下事实实现的:通用类的序列化器需要序列化器作为其通用参数在运行时传入(因此代理对象是通用的)。

这段代码说明了这样的解决方案。请注意,这仅适用于 Kotlin 2.0.20 或更高版本以及 Kotlin JSON 序列化

1.7.2 或更高版本。2

/** * We keep the generated serializer so that we can use it at the same time as the user-defined serializer * [DtoDeserializer]. This requires Kotlin 2.0.20 or later and Kotlin JSON serialization library [1.7.2](https://github.com/Kotlin/kotlinx.serialization/releases/tag/v1.7.2) * or later. */ @OptIn(ExperimentalSerializationApi::class) @Serializable(DtoDeserializer::class) @KeepGeneratedSerializer data class Dto( val id: String, val name: String, val category: String? ) class DtoDeserializer : KSerializer<Dto> { private val searchResponseSerializer = SearchResponse.serializer(Dto.generatedSerializer()) override val descriptor: SerialDescriptor get() = searchResponseSerializer.descriptor override fun deserialize(decoder: Decoder): Dto { return searchResponseSerializer.deserialize(decoder).data } override fun serialize(encoder: Encoder, value: Dto) { error("Not used as Dto is not serialized by app") } /** * We make this class generic so that we can supply the serializer for [T] (namely the serializer for * [Dto] at runtime */ @Serializable private data class SearchResponse<T>( val data: T ) }


1实际上,只有当您的 API 实际上为 String

 属性返回 
id
 时才会出现这种情况。我认为这是你写问题时的一个错误。

2在早期版本中,您需要另一个看起来与 Dto

 完全相同的代理类来为 
data
DtoDeserializer.SearchResponse
 属性生成所需的序列化器。

© www.soinside.com 2019 - 2024. All rights reserved.