如何serialize/deserialize Kotlin 封class?
How to serialize/deserialize Kotlin sealed class?
我有下面的封class:
sealed class ViewModel {
data class Loaded(val value : String) : ViewModel()
object Loading : ViewModel()
}
如何 serialize/deserialize ViewModel class 实例,比方说 to/from JSON 格式?
我试过使用 Genson serializer/deserializer 库 - 它可以处理 Kotlin 数据 classes,它也可以支持多态类型(例如,使用一些元数据来指定具体类型)。
但是,库在 Kotlin object
类型上失败,因为这些是没有 public 构造函数的单例。我想我可以编写一个自定义的 Genson 转换器来处理它,但也许有更简单的方法来做到这一点?
关于创建自定义序列化程序,您可能是正确的。
我尝试使用 Jackson 库和 Kotlin 序列化和反序列化您的 class。
这些是 Jackson 的 Maven 依赖项:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
您可以使用此库将密封的 class 序列化为 JSON,无需额外的自定义序列化程序,但反序列化需要自定义反序列化程序。
下面是我用来序列化和反序列化你的密封的玩具代码class:
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
sealed class ViewModel {
data class Loaded(val value: String) : ViewModel()
object Loading : ViewModel()
}
// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
val node: JsonNode? = jp?.getCodec()?.readTree(jp)
val value = node?.get("value")
return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
}
}
fun main(args: Array<String>) {
val m = createCustomMapper()
val ser1 = m.writeValueAsString(ViewModel.Loading)
println(ser1)
val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
println(ser2)
val deserialized1 = m.readValue(ser1, ViewModel::class.java)
val deserialized2 = m.readValue(ser2, ViewModel::class.java)
println(deserialized1)
println(deserialized2)
}
// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
val m = ObjectMapper()
val sm = SimpleModule()
sm.addDeserializer(ViewModel::class.java, ViewModelDeserializer())
m.registerModule(sm)
return m
}
如果你运行这个代码这是输出:
{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)
我最终实现了一个自定义转换器和一个工厂,以将其正确插入 Genson。
它使用 Genson 的元数据约定将对象表示为:
{
"@class": "com.example.ViewModel.Loading"
}
转换器假定设置了useClassMetadata标志,因此序列化只需要标记一个空对象。对于反序列化,它从元数据中解析 class name,加载它并获得 objectInstance.
object KotlinObjectConverter : Converter<Any> {
override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
with(writer) {
// just empty JSON object, class name will be automatically added as metadata
beginObject()
endObject()
}
}
override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
Class.forName(reader.nextObjectMetadata().metadata("class"))
.kotlin.objectInstance
.also { reader.endObject() }
}
为了确保此转换器仅应用于实际的 对象 ,我使用工厂注册它,告诉 Genson 何时使用它以及何时回退到默认值实施。
object KotlinConverterFactory : Factory<Converter<Any>> {
override fun create(type: Type, genson: Genson): Converter<Any>? =
if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
else null
}
工厂可用于通过构建器配置 Genson:
GensonBuilder()
.withConverterFactory(KotlinConverterFactory)
.useClassMetadata(true) // required to add metadata during serialization
// some other properties
.create()
使用链式转换器功能,代码可能会更好,但我还没有时间检查它。
我最近遇到了类似的问题(虽然使用的是 Jackson,而不是 Genson。)
假设我有以下内容:
sealed class Parent(val name: String)
object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")
然后在密封的class上加一个JsonCreator
函数:
sealed class Parent(val name: String) {
private companion object {
@JsonCreator
@JvmStatic
fun findBySimpleClassName(simpleName: String): Parent? {
return Parent::class.sealedSubclasses.first {
it.simpleName == simpleName
}.objectInstance
}
}
}
现在您可以在 json 属性.
中使用 ChildOne
或 ChildTwo
作为 key
进行反序列化
不需要 @JsonCreator
和 sealdSubClass
。 Jackson 在其 jackson-module-kotlin
中有此支持,只需要一个注释 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
sealed class SuperClass{
class A: SuperClass()
class B: SuperClass()
}
...
val mapper = jacksonObjectMapper()
val root: SuperClass = mapper.readValue(json)
when(root){
is A -> "It's A"
is B -> "It's B"
}
上面的例子是从它的主要仓库 README 复制而来的:https://github.com/FasterXML/jackson-module-kotlin
我有下面的封class:
sealed class ViewModel {
data class Loaded(val value : String) : ViewModel()
object Loading : ViewModel()
}
如何 serialize/deserialize ViewModel class 实例,比方说 to/from JSON 格式?
我试过使用 Genson serializer/deserializer 库 - 它可以处理 Kotlin 数据 classes,它也可以支持多态类型(例如,使用一些元数据来指定具体类型)。
但是,库在 Kotlin object
类型上失败,因为这些是没有 public 构造函数的单例。我想我可以编写一个自定义的 Genson 转换器来处理它,但也许有更简单的方法来做到这一点?
关于创建自定义序列化程序,您可能是正确的。
我尝试使用 Jackson 库和 Kotlin 序列化和反序列化您的 class。
这些是 Jackson 的 Maven 依赖项:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
您可以使用此库将密封的 class 序列化为 JSON,无需额外的自定义序列化程序,但反序列化需要自定义反序列化程序。
下面是我用来序列化和反序列化你的密封的玩具代码class:
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
sealed class ViewModel {
data class Loaded(val value: String) : ViewModel()
object Loading : ViewModel()
}
// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
val node: JsonNode? = jp?.getCodec()?.readTree(jp)
val value = node?.get("value")
return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
}
}
fun main(args: Array<String>) {
val m = createCustomMapper()
val ser1 = m.writeValueAsString(ViewModel.Loading)
println(ser1)
val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
println(ser2)
val deserialized1 = m.readValue(ser1, ViewModel::class.java)
val deserialized2 = m.readValue(ser2, ViewModel::class.java)
println(deserialized1)
println(deserialized2)
}
// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
val m = ObjectMapper()
val sm = SimpleModule()
sm.addDeserializer(ViewModel::class.java, ViewModelDeserializer())
m.registerModule(sm)
return m
}
如果你运行这个代码这是输出:
{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)
我最终实现了一个自定义转换器和一个工厂,以将其正确插入 Genson。
它使用 Genson 的元数据约定将对象表示为:
{
"@class": "com.example.ViewModel.Loading"
}
转换器假定设置了useClassMetadata标志,因此序列化只需要标记一个空对象。对于反序列化,它从元数据中解析 class name,加载它并获得 objectInstance.
object KotlinObjectConverter : Converter<Any> {
override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
with(writer) {
// just empty JSON object, class name will be automatically added as metadata
beginObject()
endObject()
}
}
override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
Class.forName(reader.nextObjectMetadata().metadata("class"))
.kotlin.objectInstance
.also { reader.endObject() }
}
为了确保此转换器仅应用于实际的 对象 ,我使用工厂注册它,告诉 Genson 何时使用它以及何时回退到默认值实施。
object KotlinConverterFactory : Factory<Converter<Any>> {
override fun create(type: Type, genson: Genson): Converter<Any>? =
if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
else null
}
工厂可用于通过构建器配置 Genson:
GensonBuilder()
.withConverterFactory(KotlinConverterFactory)
.useClassMetadata(true) // required to add metadata during serialization
// some other properties
.create()
使用链式转换器功能,代码可能会更好,但我还没有时间检查它。
我最近遇到了类似的问题(虽然使用的是 Jackson,而不是 Genson。)
假设我有以下内容:
sealed class Parent(val name: String)
object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")
然后在密封的class上加一个JsonCreator
函数:
sealed class Parent(val name: String) {
private companion object {
@JsonCreator
@JvmStatic
fun findBySimpleClassName(simpleName: String): Parent? {
return Parent::class.sealedSubclasses.first {
it.simpleName == simpleName
}.objectInstance
}
}
}
现在您可以在 json 属性.
中使用ChildOne
或 ChildTwo
作为 key
进行反序列化
不需要 @JsonCreator
和 sealdSubClass
。 Jackson 在其 jackson-module-kotlin
中有此支持,只需要一个注释 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
sealed class SuperClass{
class A: SuperClass()
class B: SuperClass()
}
...
val mapper = jacksonObjectMapper()
val root: SuperClass = mapper.readValue(json)
when(root){
is A -> "It's A"
is B -> "It's B"
}
上面的例子是从它的主要仓库 README 复制而来的:https://github.com/FasterXML/jackson-module-kotlin