Ktor:Serialize/Deserialize JSON 在 Multiplatform 中以 List 为根
Ktor: Serialize/Deserialize JSON with List as root in Multiplatform
我们如何将 kotlin.serialize 与 Ktor 的 HttpClient 一起使用到 deserialize/serialize JSON 与列表作为根?我正在创建 HttpClient 如下:
HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
setMapper(MyClass::class, MyClass.serializer())
setMapper(AnotherClass::class, AnotherClass.serializer())
}
}
install(ExpectSuccess)
}
看来我需要为 List 设置 setMapper,但是这对于泛型是不可能的。我看到我可以使用 MyClass.serializer().list 为它获取序列化器,但是在 http 请求上将它注册到 deserialize/serialize 并不是直接的。有人知道好的解决方案吗?
使用 ktor 1.3.0 更新:
现在您可以直接从客户端接收默认集合(例如列表):
@Serializable
data class User(val id: Int)
val response: List<User> = client.get(...)
// or client.get<List<User>>(...)
ktor 1.3.0 之前:
还没有办法在 kotlinx.serialization 中(反)序列化这样的 JSON。
对于序列化,你可以尝试这样的事情:
fun serializer(data: Any) = if (data is List<*>) {
if (data is EmptyList) String::class.serializer().list // any class with serializer
else data.first()::class.serializer().list
} else data.serializer()
并且没有已知的方法来获取列表反序列化器。
这更像是一种解决方法,但在逐步完成 KotlinxSerializer
代码后,我看不到任何其他解决方法。例如,如果您查看 KotlinxSerializer.read()
,您会发现它尝试查找基于 type
的 mapper
,但在本例中它只是一个 kotlin.collections.List
并且无法解析。我曾尝试调用类似 setListMapper(MyClass::class, MyClass.serializer())
的方法,但这仅适用于序列化(使用 write
中的 lookupSerializerByData
方法)
override suspend fun read(type: TypeInfo, response: HttpResponse): Any {
val mapper = lookupSerializerByType(type.type)
val text = response.readText()
@Suppress("UNCHECKED_CAST")
return json.parse(mapper as KSerializer<Any>, text)
}
所以,我最终做的是(注意 serializer().list
调用)
suspend fun fetchBusStops(): List<BusStop> {
val jsonArrayString = client.get<String> {
url("$baseUrl/stops.json")
}
return JSON.nonstrict.parse(BusStop.serializer().list, jsonArrayString)
}
不理想,显然没有使用 JsonFeature
。
您可以编写包装器和自定义序列化程序:
@Serializable
class MyClassList(
val items: List<MyClass>
) {
@Serializer(MyClassList::class)
companion object : KSerializer<MyClassList> {
override val descriptor = StringDescriptor.withName("MyClassList")
override fun serialize(output: Encoder, obj: MyClassList) {
MyClass.serializer().list.serialize(output, obj.items)
}
override fun deserialize(input: Decoder): MyClassList {
return MyClassList(MyClass.serializer().list.deserialize(input))
}
}
}
注册它:
HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
setMapper(MyClassList::class, MyClassList.serializer())
}
}
}
并使用:
suspend fun fetchItems(): List<MyClass> {
return client.get<MyClassList>(URL).items
}
我碰巧在 Kotlin/JS 上遇到了同样的问题,并设法通过这种方式解决了它:
private val client = HttpClient(Js) {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
register(User.serializer().list)
}
}
}
...
private suspend fun fetchUsers(): Sequence<User> =
client.get<List<User>> {
url("$baseUrl/users")
}.asSequence()
希望这对您有所帮助:)
我们如何将 kotlin.serialize 与 Ktor 的 HttpClient 一起使用到 deserialize/serialize JSON 与列表作为根?我正在创建 HttpClient 如下:
HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
setMapper(MyClass::class, MyClass.serializer())
setMapper(AnotherClass::class, AnotherClass.serializer())
}
}
install(ExpectSuccess)
}
看来我需要为 List 设置 setMapper,但是这对于泛型是不可能的。我看到我可以使用 MyClass.serializer().list 为它获取序列化器,但是在 http 请求上将它注册到 deserialize/serialize 并不是直接的。有人知道好的解决方案吗?
使用 ktor 1.3.0 更新:
现在您可以直接从客户端接收默认集合(例如列表):
@Serializable
data class User(val id: Int)
val response: List<User> = client.get(...)
// or client.get<List<User>>(...)
ktor 1.3.0 之前:
还没有办法在 kotlinx.serialization 中(反)序列化这样的 JSON。
对于序列化,你可以尝试这样的事情:
fun serializer(data: Any) = if (data is List<*>) {
if (data is EmptyList) String::class.serializer().list // any class with serializer
else data.first()::class.serializer().list
} else data.serializer()
并且没有已知的方法来获取列表反序列化器。
这更像是一种解决方法,但在逐步完成 KotlinxSerializer
代码后,我看不到任何其他解决方法。例如,如果您查看 KotlinxSerializer.read()
,您会发现它尝试查找基于 type
的 mapper
,但在本例中它只是一个 kotlin.collections.List
并且无法解析。我曾尝试调用类似 setListMapper(MyClass::class, MyClass.serializer())
的方法,但这仅适用于序列化(使用 write
中的 lookupSerializerByData
方法)
override suspend fun read(type: TypeInfo, response: HttpResponse): Any {
val mapper = lookupSerializerByType(type.type)
val text = response.readText()
@Suppress("UNCHECKED_CAST")
return json.parse(mapper as KSerializer<Any>, text)
}
所以,我最终做的是(注意 serializer().list
调用)
suspend fun fetchBusStops(): List<BusStop> {
val jsonArrayString = client.get<String> {
url("$baseUrl/stops.json")
}
return JSON.nonstrict.parse(BusStop.serializer().list, jsonArrayString)
}
不理想,显然没有使用 JsonFeature
。
您可以编写包装器和自定义序列化程序:
@Serializable
class MyClassList(
val items: List<MyClass>
) {
@Serializer(MyClassList::class)
companion object : KSerializer<MyClassList> {
override val descriptor = StringDescriptor.withName("MyClassList")
override fun serialize(output: Encoder, obj: MyClassList) {
MyClass.serializer().list.serialize(output, obj.items)
}
override fun deserialize(input: Decoder): MyClassList {
return MyClassList(MyClass.serializer().list.deserialize(input))
}
}
}
注册它:
HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
setMapper(MyClassList::class, MyClassList.serializer())
}
}
}
并使用:
suspend fun fetchItems(): List<MyClass> {
return client.get<MyClassList>(URL).items
}
我碰巧在 Kotlin/JS 上遇到了同样的问题,并设法通过这种方式解决了它:
private val client = HttpClient(Js) {
install(JsonFeature) {
serializer = KotlinxSerializer().apply {
register(User.serializer().list)
}
}
}
...
private suspend fun fetchUsers(): Sequence<User> =
client.get<List<User>> {
url("$baseUrl/users")
}.asSequence()
希望这对您有所帮助:)