【问题标题】:GSON Deserialization of subtypes in KotlinKotlin 中子类型的 GSON 反序列化
【发布时间】:2019-09-27 12:52:03
【问题描述】:

我不确定这是限制、错误还是 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的反序列化?

(deps:com.google.code.gson:gson:2.8.5org.danilopianini:gson-extras:0.2.1

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

【问题讨论】:

  • 你能发布错误日志吗?

标签: kotlin gson deserialization


【解决方案1】:

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

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

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内部类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&lt;? super T&gt;) 方法可以解决缺少无参数构造函数的问题,但使用RuntimeTypeAdapterFactory 无法正常工作。

【讨论】:

  • 即使在不使用RuntimeTypeAdapterFactory 时也可以使用
  • @Mohanakrrishna 实际上它对我有用 RuntimeTypeAdapterFactory,将其标记为正确答案并“支付”赏金。
  • @zdenda.online 伙计,你真的等到最后一分钟才授予赏金!我没想到会得到任何奖励;我以为你忘记了你的赏金。非常感谢,谢谢!
  • @Enselic 不,我只是有点失去希望,有一段时间没有访问 stackoverflow ;)
【解决方案2】:

我可能遗漏了您想要实现的目标,但是否有必要使用RuntimeTypeAdapterFactory?如果我们取出我们在 Gson 构建器中注册的那一行,那么它会读取

val gson = GsonBuilder()
    .create()

然后输出返回我们期望的枚举,它看起来是正确的序列化/反序列化。 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
    }
}

【讨论】:

  • 感谢尼尔森的回答。我需要 RuntimeAdapterFactory 因为每个子类型都有不同的字段集。我更新了原来的答案,很清楚。我不想弄乱示例代码,所以它只指出了问题所在。将 Serializable 添加到 Parent 没有帮助:-(
【解决方案3】:

尝试为每个枚举添加@SerializedName 注解。

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

【讨论】:

  • 感谢您的提示。我已经(之前)尝试过发布问题,但这没有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
相关资源
最近更新 更多