【问题标题】:Ktor: Serialize/Deserialize JSON with List as root in MultiplatformKtor:在多平台中以 List 为根序列化/反序列化 JSON
【发布时间】:2019-03-28 23:47:40
【问题描述】:

我们如何使用 kotlin.serialize 和 Ktor 的 HttpClient 来反序列化/序列化 JSON 并以列表为根?我正在按如下方式创建 HttpClient:

HttpClient {
       install(JsonFeature) {
           serializer = KotlinxSerializer().apply {
               setMapper(MyClass::class, MyClass.serializer())
               setMapper(AnotherClass::class, AnotherClass.serializer())
           }
       }
       install(ExpectSuccess)
   }

看来我需要为 List 设置映射器,但是这对于泛型是不可能的。我看到我可以使用 MyClass.serializer().list 为它获取序列化程序,但是将其注册为对 http 请求进行反序列化/序列化并不简单。有人知道一个好的解决方案吗?

【问题讨论】:

  • 看到有一个KotlinxSerializer.registerList() ....试过了,但仍然得到“找不到类 kotlin.collections.List 的无参数序列化程序。对于泛型类,例如列表,请明确提供序列化程序。”错误
  • 以下还有一些可能相关的讨论(虽然还没有弄清楚如何在使用HttpClient - github.com/Kotlin/kotlinx.serialization/issues/179时应用它

标签: json serialization kotlin ktor kotlin-multiplatform


【解决方案1】:

您可以编写包装器和自定义序列化器:

@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
}

【讨论】:

  • 不错的解决方案,其实我们不需要在第2步注册,Ktor会自动应用MyClassList.serializer()
  • 我确实需要注册,否则只能用于调试(progard 以某种方式破坏它)
  • 违背了拥有泛型的目的。希望这很快得到更好的解决方案
【解决方案2】:

使用 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()

并且没有已知的方法来获取列表反序列化器。

【讨论】:

  • 感谢您的回复。这样的事情对我有用,直到。
  • 顺便说一句,我们将在 1.3.0 中引入 typeOf 功能支持。
  • @Leonid 既然 1.3.0 可用,您能否编辑此答案以使用 typeOf?谢谢!
  • client.get 对我来说是黄金。
【解决方案3】:

这更像是一种解决方法,但在单步执行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

【讨论】:

    【解决方案4】:

    我碰巧在 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()
    

    希望这会有所帮助:)

    【讨论】:

    • 仅当您仅注册一个列表序列化程序时才有效。当有更多时,KotlinxSerializer.lookupSerializerByType() 不会区分它们并且无法预测地选择。
    猜你喜欢
    • 2021-01-22
    • 1970-01-01
    • 2018-10-06
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 2021-06-14
    • 2015-06-07
    • 2016-10-08
    相关资源
    最近更新 更多