我使用 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: $
您使用包装类的 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
属性生成所需的序列化器。