在kotlin中使用Gson序列化数据类中的Sealed类



我为CustomAttribute数据类下的json字段Value创建了一个密封类。此字段可以返回StringArray of Strings

我们如何从json反序列化这个密封类?

data class CustomAttribute (
val attributeCode: String,
val value: Value 
)
sealed class Value {
class StringArrayValue(val value: List<String>) : Value()
class StringValue(val value: String)            : Value()
}

我创建了一个TypeAdapterFactory实现,专门用于支持密封类及其子类型。这与RuntimeTypeAdapterFactory的工作原理类似(我用它作为编写类的指南(,但它将专门支持密封类型,并将使用具有密封类超类型的对象的对象实例进行反序列化(RuntimeTypeAdapterFactory将创建对象类型的新实例,当期望单个实例时,它会中断相等性检查(。

private class SealedTypeAdapterFactory<T : Any> private constructor(
private val baseType: KClass<T>,
private val typeFieldName: String
) : TypeAdapterFactory {
private val subclasses = baseType.sealedSubclasses
private val nameToSubclass = subclasses.associateBy { it.simpleName!! }
init {
if (!baseType.isSealed) throw IllegalArgumentException("$baseType is not a sealed class")
}
override fun <R : Any> create(gson: Gson, type: TypeToken<R>?): TypeAdapter<R>? {
if (type == null || subclasses.isEmpty() || subclasses.none { type.rawType.isAssignableFrom(it.java) }) return null
val elementTypeAdapter = gson.getAdapter(JsonElement::class.java)
val subclassToDelegate: Map<KClass<*>, TypeAdapter<*>> = subclasses.associateWith {
gson.getDelegateAdapter(this, TypeToken.get(it.java))
}
return object : TypeAdapter<R>() {
override fun write(writer: JsonWriter, value: R) {
val srcType = value::class
val label = srcType.simpleName!!
@Suppress("UNCHECKED_CAST") val delegate = subclassToDelegate[srcType] as TypeAdapter<R>
val jsonObject = delegate.toJsonTree(value).asJsonObject
if (jsonObject.has(typeFieldName)) {
throw JsonParseException("cannot serialize $label because it already defines a field named $typeFieldName")
}
val clone = JsonObject()
clone.add(typeFieldName, JsonPrimitive(label))
jsonObject.entrySet().forEach {
clone.add(it.key, it.value)
}
elementTypeAdapter.write(writer, clone)
}
override fun read(reader: JsonReader): R {
val element = elementTypeAdapter.read(reader)
val labelElement = element.asJsonObject.remove(typeFieldName) ?: throw JsonParseException(
"cannot deserialize $baseType because it does not define a field named $typeFieldName"
)
val name = labelElement.asString
val subclass = nameToSubclass[name] ?: throw JsonParseException("cannot find $name subclass of $baseType")
@Suppress("UNCHECKED_CAST")
return (subclass.objectInstance as? R) ?: (subclassToDelegate[subclass]!!.fromJsonTree(element) as R)
}
}
}
companion object {
fun <T : Any> of(clz: KClass<T>) = SealedTypeAdapterFactory(clz, "type")
}
}

用法:

GsonBuilder().registerTypeAdapter(SealedTypeAdapterFactory.of(Value::class)).create()

如果目标只是序列化,下面的代码就可以了。如果你一直使用旧的Gson版本,并且也想反序列化,Jasons-SealedTypeAdapterFactory可以很好地工作。

private val serializer = JsonSerializer<MySuperclass> { src, _, context ->
context!!.serialize(src)
.also { it.asJsonObject.addProperty("type", src?.javaClass?.simpleName) }

val GSON: Gson = GsonBuilder()
.registerTypeAdapter(MySuperclass::class.java, serializer)
.create()

我过去曾成功地序列化和反序列化过一个密封类,并声明使用Jackson而不是Gson作为我的序列化引擎。

我的密封类被定义为:

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, visible = true)
sealed class FlexibleResponseModel
class SnapshotResponse(val collection: List<EntityModel>): FlexibleResponseModel()
class DifferentialResponse(val collection: List<EntityModel>): FlexibleResponseModel()
class EventDrivenResponse(val collection: List<EntityEventModel>): FlexibleResponseModel()
class ErrorResponse(val error: String): FlexibleResponseModel()

使用了注释,Jackson实例不需要进一步的配置就可以正确地序列化和反序列化这个密封类的实例,因为通信双方都拥有密封类的统一定义。

虽然我认识到JsonTypeInfo是一个特定于Jackson的注释,但如果必须使用此功能,您可能会考虑从Gson切换,或者您可能能够为Gson找到一个等效的配置,该配置还将在序列化数据中包括类标识符。

最新更新