Kotlinx 序列化:如何绕过反序列化的具体类型参数?
Kotlinx Serialization: How to circumvent reified typeargs for deserialization?
实际上,主要问题仍然是 Kotlin 中没有 classes 的具体化类型参数。但这就是为什么在这种特定情况下这让我感到困扰:
假设你有一个包装器 class Wrapper
,它接受一个字符串 content
和一个 class* type
并且可以输出一个对象 class type
通过调用函数 getObj()
:
将 content
解析为 JSON 按需检索
class Wrapper<T>(private val content: String, /*private val type: KClass<*>*/) {
fun getObj(): T {
// ?
}
}
我想使用 kotlinx.serialization。现在,您可能已经注意到我之前是如何在“class”之后加上星号的。原因如下:是的,Wrapper
必须以 some 的方式获取目标 class,但是如何呢?它应该只是 typearg(因为键入 erausre 而不起作用)还是 KClass
引用(因为我需要具体化的 typearg 而不起作用)?
据我所知,将通用 JSON 解码为可序列化目标 class 的唯一方法是使用 Json.decodeFromString<T>(content)
,其中 T
是目标类型,content
是 JSON 字符串。现在,T
被定义为具体化(以便可以在运行时处理该类型)并且只能用另一个具体化的类型参数或实际的 class 引用填充。我不能使用另一个具体化的类型参数,因为我在 class 的上下文中,而 class 不能有具体化的类型参数。我也不能使用实际的 class 引用,因为 class 的用户应该能够使用不同的目标构建它,例如他们决定目标是什么,而不是我。
那么,如何使用 kotlinx.serialization 执行此操作?有可能吗?
好的,所以还没有人回答这个问题,但我也在 r/Kotlin subreddit 中发布了这个问题。 Here it is.
我实际上在那里得到了答案(归功于 u/JakeWharton),并且由于您可能会遇到这个 Whosebug 问题,因为您在 google 上搜索了相同的问题,您可能很乐意在这里找到答案。所以这是我尝试解释的答案:
所以,基本上,kotlinx-serialization 确实不适用于 KClass
es。但是仔细一想,只需要KClass
就可以确定怎么序列化了。因为当你使用 KXS 时,这是在 compile-time 确定的,你实际上只需要传递序列化器(定义如何序列化/反序列化你的 class 的实际策略)。您可以通过调用 .serializer()
为每个用 @Serializable
注释的 class 获得一个序列化程序;结果将是 KSerializer<T>
类型。所以,而不是
class Wrapper<T>(private val content: String, private val type: KClass<T>)
并通过
构建它
val wrapper = Wrapper("{}", Foo::class)
你可以这样做:
class Wrapper<T>(private val content: String, private val serializer: KSerializer<T>)
然后像这样构造它:
val wrapper = Wrapper("{}", Foo.serializer())
(假设 Foo
被注释为 @Serializable
)
然后您可以使用 KSerializer
而不是 typearg 来序列化和反序列化,如下所示:
val obj: T = Json.decodeFromString(serializer, "[Your JSON String]")
val str: String = Json.encodeToString(serializer, obj)
就是这样!只需将您的常规 (K)Class
方法换成 KSerializer
,它就可以与 KXS 一起使用。
实际上,主要问题仍然是 Kotlin 中没有 classes 的具体化类型参数。但这就是为什么在这种特定情况下这让我感到困扰:
假设你有一个包装器 class Wrapper
,它接受一个字符串 content
和一个 class* type
并且可以输出一个对象 class type
通过调用函数 getObj()
:
content
解析为 JSON 按需检索
class Wrapper<T>(private val content: String, /*private val type: KClass<*>*/) {
fun getObj(): T {
// ?
}
}
我想使用 kotlinx.serialization。现在,您可能已经注意到我之前是如何在“class”之后加上星号的。原因如下:是的,Wrapper
必须以 some 的方式获取目标 class,但是如何呢?它应该只是 typearg(因为键入 erausre 而不起作用)还是 KClass
引用(因为我需要具体化的 typearg 而不起作用)?
据我所知,将通用 JSON 解码为可序列化目标 class 的唯一方法是使用 Json.decodeFromString<T>(content)
,其中 T
是目标类型,content
是 JSON 字符串。现在,T
被定义为具体化(以便可以在运行时处理该类型)并且只能用另一个具体化的类型参数或实际的 class 引用填充。我不能使用另一个具体化的类型参数,因为我在 class 的上下文中,而 class 不能有具体化的类型参数。我也不能使用实际的 class 引用,因为 class 的用户应该能够使用不同的目标构建它,例如他们决定目标是什么,而不是我。
那么,如何使用 kotlinx.serialization 执行此操作?有可能吗?
好的,所以还没有人回答这个问题,但我也在 r/Kotlin subreddit 中发布了这个问题。 Here it is.
我实际上在那里得到了答案(归功于 u/JakeWharton),并且由于您可能会遇到这个 Whosebug 问题,因为您在 google 上搜索了相同的问题,您可能很乐意在这里找到答案。所以这是我尝试解释的答案:
所以,基本上,kotlinx-serialization 确实不适用于 KClass
es。但是仔细一想,只需要KClass
就可以确定怎么序列化了。因为当你使用 KXS 时,这是在 compile-time 确定的,你实际上只需要传递序列化器(定义如何序列化/反序列化你的 class 的实际策略)。您可以通过调用 .serializer()
为每个用 @Serializable
注释的 class 获得一个序列化程序;结果将是 KSerializer<T>
类型。所以,而不是
class Wrapper<T>(private val content: String, private val type: KClass<T>)
并通过
构建它val wrapper = Wrapper("{}", Foo::class)
你可以这样做:
class Wrapper<T>(private val content: String, private val serializer: KSerializer<T>)
然后像这样构造它:
val wrapper = Wrapper("{}", Foo.serializer())
(假设 Foo
被注释为 @Serializable
)
然后您可以使用 KSerializer
而不是 typearg 来序列化和反序列化,如下所示:
val obj: T = Json.decodeFromString(serializer, "[Your JSON String]")
val str: String = Json.encodeToString(serializer, obj)
就是这样!只需将您的常规 (K)Class
方法换成 KSerializer
,它就可以与 KXS 一起使用。