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);
即它试图通过不带参数的构造函数构造一个对象。对于 Subtype2
和 Subtype3
,代码将导致捕获异常:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
即您的原始代码失败,因为 Gson 无法在没有 Subtype2
和 Subtype3
.
参数的情况下找到构造函数
在简单的情况下,ConstructorConstructor
中的 newUnsafeAllocator(Type, final Class<? super T>)
方法解决了缺少无参数构造函数的问题,但 RuntimeTypeAdapterFactory
无法正常工作。
我不确定这是限制、错误还是 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);
即它试图通过不带参数的构造函数构造一个对象。对于 Subtype2
和 Subtype3
,代码将导致捕获异常:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
即您的原始代码失败,因为 Gson 无法在没有 Subtype2
和 Subtype3
.
在简单的情况下,ConstructorConstructor
中的 newUnsafeAllocator(Type, final Class<? super T>)
方法解决了缺少无参数构造函数的问题,但 RuntimeTypeAdapterFactory
无法正常工作。