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(),您会发现它尝试查找基于 typemapper,但在本例中它只是一个 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()

希望这对您有所帮助:)