Kotlin中子类型的GSON反序列化

GSON Deserialization of subtypes in Kotlin

我不确定这是限制、错误还是 GSON 使用不当。我需要有一个 Kotlin 对象的层次结构(具有各种子类型的父对象),我需要用 GSON 反序列化它们。反序列化的对象具有正确的子类型 但其字段 enumField 实际上为 null

首先我认为这是因为该字段被传递给 "super" 构造函数,但后来我发现 "super" 适用于字符串,只是枚举被破坏了。

看这个例子:

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory

open class Parent(val stringField: String,
                  val enumField: EnumField) {

    enum class EnumField {
        SUBTYPE1,
        SUBTYPE2,
        SUBTYPE3
    }
}


class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2)
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type)

val subtypeRAF = RuntimeTypeAdapterFactory.of(Parent::class.java, "enumField")
        .registerSubtype(Subtype1::class.java, Parent.EnumField.SUBTYPE1.name)
        .registerSubtype(Subtype2::class.java, Parent.EnumField.SUBTYPE2.name)
        .registerSubtype(Subtype3::class.java, Parent.EnumField.SUBTYPE3.name)

fun main() {
    val gson = GsonBuilder()
            .registerTypeAdapterFactory(subtypeRAF)
            .create()

    serializeAndDeserialize(gson, Subtype1()) // this works (but not suitable)
    serializeAndDeserialize(gson, Subtype2("s2")) // broken
    serializeAndDeserialize(gson, Subtype3("s3", Parent.EnumField.SUBTYPE3)) // broken
}

private fun serializeAndDeserialize(gson: Gson, obj: Parent) {
    println("-----------------------------------------")
    val json = gson.toJson(obj)
    println(json)
    val obj = gson.fromJson(json, Parent::class.java)
    println("stringField=${obj.stringField}, enumField=${obj.enumField}")
}

关于如何实现反序列化 enumField 的任何想法?

(部门:com.google.code.gson:gson:2.8.5,org.danilopianini:gson-extras:0.2.1)

P.S.: 请注意,我必须使用 RuntimeAdapterFactory 因为我有具有不同字段集的子类型(我没有在示例中这样做所以更容易理解)。

尝试向每个枚举添加 @SerializedName 注释。

enum class EnumField {
    @SerializedName("subtype1")
    SUBTYPE1,
    @SerializedName("subtype2")
    SUBTYPE2,
    @SerializedName("subtype3")
    SUBTYPE3
}

我可能遗漏了一些你想要实现的东西,但是有必要使用 RuntimeTypeAdapterFactory 吗?如果我们删除我们在 Gson 构建器中注册的行,那么它会显示

val gson = GsonBuilder()
    .create()

然后输出 returns 我们期望的枚举,它看起来是正确的序列化/反序列化。 IE。输出是:

-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3

在 Parent 中实现 Serializable 也可能是一个想法。即

open class Parent(val stringField: String, val enumField: EnumField) : Serializable {

    enum class EnumField {
        SUBTYPE1,
        SUBTYPE2,
        SUBTYPE3
    }
}

Gson 需要没有参数的构造函数才能正常工作(请参阅下面深入了解 Gson 代码)。 Gson 构造原始对象,然后使用反射用值填充字段。

因此,如果您只是向缺少它们的 classes 添加一些无参数的虚拟构造函数,如下所示:

class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2) {
    constructor() : this("")
}
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type) {
    constructor() : this("", EnumField.SUBTYPE3)
}

您将得到预期的输出:

-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3

Gson 深入研究

如果你想研究 Gson 的内部结构,一个技巧是在 Subtype1 中添加一个 init { } 块,因为它可以工作,然后在那里设置一个断点。命中后,您可以向上移动调用堆栈、逐步执行代码、设置更多断点等,以揭示 Gson 如何构造对象的细节。

通过这个方法,你可以找到Gson内部class com.google.gson.internal.ConstructorConstructor和它的方法newDefaultConstructor(Class<? super T>)有这样的代码(为了简洁我做了简化):

    final Constructor<? super T> constructor = rawType.getDeclaredConstructor(); // rawType is e.g. 'class Subtype3'
    Object[] args = null;
    return (T) constructor.newInstance(args);

即它试图通过不带参数的构造函数构造一个对象。对于 Subtype2Subtype3,代码将导致捕获异常:

    } catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
      return null; // set breakpoint here to see
    }

即您的原始代码失败,因为 Gson 无法在没有 Subtype2Subtype3.

参数的情况下找到构造函数

在简单的情况下,ConstructorConstructor 中的 newUnsafeAllocator(Type, final Class<? super T>) 方法解决了缺少无参数构造函数的问题,但 RuntimeTypeAdapterFactory 无法正常工作。