Gson根据父属性反序列化子对象
我正在尝试反序列化一个 json 响应,该响应包含具有子对象的对象,这些子对象可以根据父类中的属性更改类型。我已经看到了如何使用类型适配器工厂在定义子对象自己的属性类型时反序列化子对象的示例,但无法弄清楚如何在父对象中定义类型的位置执行此操作。这可能吗?
JSON 示例
{
"items": [
{
"someProperty": "here",
"anotherProperty": "there",
"childProperty": {
"foo": "This property will be here if itemType is 'foo'"
"abc": "def"
},
"itemType": "foo",
},
{
"someProperty": "here",
"anotherProperty": "there",
"childProperty": {
"bar": "This property will be here if itemType is 'bar'"
"ghi": "jkl"
},
"itemType": "bar",
}
],
"limit": 25,
"nextCursor": null
}
在上面的示例中,childPropertyThatChanges
应根据 itemType
的值反序列化为不同的类型。
给定下面的序列化类:
data class FooBarWrapper(
val items: List<ParentItem>,
val limit: Int,
val nextCursor: String?
) : Serializable
data class ParentItem(
val someProperty: String,
val anotherProperty: String,
val childProperty: ChildProperty
)
open class ChildProperty
data class ChildPropertyFoo(
val foo: String,
val abc: String
) : ChildProperty()
data class ChildPropertyBar(
val bar: String,
val ghi: String
) : ChildProperty()
类型适配器为:
val exampleTypeAdapter = RuntimeTypeAdapterFactory
.of(ChildProperty::class.java, "itemType")
.registerSubtype(ChildPropertyFoo::class.java, "foo")
.registerSubtype(ChildPropertyBar::class.java, "bar")
val exampleGson = GsonBuilder()
.registerTypeAdapterFactory(exampleTypeAdapter)
.create()
val deserialized = exampleGson.fromJson(exampleJson, FooBarWrapper::class.java)
在上面的示例中,childProperty
永远不会反序列化 - 它保持为 null,因为它无法推断类型,因为 itemType
存在于父对象中。
但是,如果我将 json 架构更改为下面的 itemType 位于子对象内的内容,则一切都可以正常反序列化。
{
"items": [{
"someProperty": "here",
"anotherProperty": "there",
"childPropertyThatChanges": {
"foo": "here when itemType is foo",
"abc": "def",
"itemType": "foo"
}
},
{
"someProperty": "here",
"anotherProperty": "there",
"childPropertyThatChanges": {
"bar": "here when itemType is bar",
"ghi": "jkl",
"itemType": "bar"
}
}
],
"limit": 25,
"nextCursor": null
}
我无法更改收到的 json,因此我试图弄清楚如何创建类型适配器,以便它能够与父对象和子对象中定义的类型一起使用。
I'm trying to deserialize a json response that contains objects which have child objects that can change type based on a property in the parent class. I've seen examples of how to use the type adapter factory for deserializing a child when it's own property type is defined, but cannot figure out how to do it where the defining type is in the parent object. Is this possible?
Example JSON
{
"items": [
{
"someProperty": "here",
"anotherProperty": "there",
"childProperty": {
"foo": "This property will be here if itemType is 'foo'"
"abc": "def"
},
"itemType": "foo",
},
{
"someProperty": "here",
"anotherProperty": "there",
"childProperty": {
"bar": "This property will be here if itemType is 'bar'"
"ghi": "jkl"
},
"itemType": "bar",
}
],
"limit": 25,
"nextCursor": null
}
In the above example, the childPropertyThatChanges
should get deserialized to a different type depending on the value of itemType
.
Given the classes for serialization below:
data class FooBarWrapper(
val items: List<ParentItem>,
val limit: Int,
val nextCursor: String?
) : Serializable
data class ParentItem(
val someProperty: String,
val anotherProperty: String,
val childProperty: ChildProperty
)
open class ChildProperty
data class ChildPropertyFoo(
val foo: String,
val abc: String
) : ChildProperty()
data class ChildPropertyBar(
val bar: String,
val ghi: String
) : ChildProperty()
And the type adapters as:
val exampleTypeAdapter = RuntimeTypeAdapterFactory
.of(ChildProperty::class.java, "itemType")
.registerSubtype(ChildPropertyFoo::class.java, "foo")
.registerSubtype(ChildPropertyBar::class.java, "bar")
val exampleGson = GsonBuilder()
.registerTypeAdapterFactory(exampleTypeAdapter)
.create()
val deserialized = exampleGson.fromJson(exampleJson, FooBarWrapper::class.java)
In the above example, the childProperty
is never deserialized - it remains null since it cannot infer the type because the itemType
lives in the parent object.
If I however change the json schema to the below where the itemType is inside the child object, everything deserializes fine.
{
"items": [{
"someProperty": "here",
"anotherProperty": "there",
"childPropertyThatChanges": {
"foo": "here when itemType is foo",
"abc": "def",
"itemType": "foo"
}
},
{
"someProperty": "here",
"anotherProperty": "there",
"childPropertyThatChanges": {
"bar": "here when itemType is bar",
"ghi": "jkl",
"itemType": "bar"
}
}
],
"limit": 25,
"nextCursor": null
}
I can't change the json that I'm receiving, so I'm trying to figure out how to create the type adapter so that it works with the type being defined in the parent vs the child object.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
使用 Gson,您可以通过实现自定义
TypeAdapterFactory
执行以下操作:ParentItem
itemType
String 到对应的TypeAdapter
,从Gson
实例获取(下文中称为“itemType
映射”)Gson
实例的JsonObject
适配器(在下文中称为“JsonObject 适配器”)ParentItem
代理适配器Gson
实例(在下文中称为“ParentItem 适配器”)(需要一个委托适配器,否则 Gson 将仅使用当前的ParentItem
工厂,导致无限递归)childProperty
值并将其存储在变量childPropertyValue
itemType
值并从itemType
映射中获取对应的TypeAdapter(以下称为“子适配器”)childPropertyValue
;Gson 不会抱怨缺少属性)childPropertyValue
上使用子适配器,并将其结果存储在先前读取的 ParentItem 对象的childProperty
中(这需要创建ParentItem.childProperty
一个var
)然后只需要使用
GsonBuilder
注册TypeAdapterFactory
(以及可选的任何自定义适配器ChildPropertyFoo
或ChildPropertyBar
)。以下是
TypeAdapterFactory
的示例实现:请注意,其他 JSON 框架提供了开箱即用的此功能,例如 Jackson 有
JsonTypeInfo.As.EXTERNAL_PROPERTY
。With Gson you could possibly solve this by implementing a custom
TypeAdapterFactory
which does the following:ParentItem
itemType
String to correspondingTypeAdapter
, obtained from theGson
instance (in the following called "itemType
map")JsonObject
from theGson
instance (in the following called "JsonObject adapter")ParentItem
from theGson
instance (in the following called "ParentItem adapter") (a delegate adapter is needed because otherwise Gson would simply use the currentParentItem
factory, resulting in infinite recursion)childProperty
value from the parsed JsonObject and store it in a variablechildPropertyValue
itemType
value and get the corresponding TypeAdapter from theitemType
map (in the following called "child adapter")childPropertyValue
; Gson will not complain about the missing property)childPropertyValue
and store its result in thechildProperty
of the previously read ParentItem object (this requires makingParentItem.childProperty
avar
)Then you only need to register that
TypeAdapterFactory
with aGsonBuilder
(and optionally any custom adapters forChildPropertyFoo
orChildPropertyBar
).Here is a sample implementation of the
TypeAdapterFactory
:Note that other JSON frameworks provide this functionality out of the box, for example Jackson has
JsonTypeInfo.As.EXTERNAL_PROPERTY
.一种方法是为
ParentItem
类创建一个类型适配器,并在JsonDeserializer
子类中根据itemType
属性的值反序列化子类具有正确类的对象(ChildPropertyFoo
或ChildPropertyBar
)。然后,您只需将反序列化的对象分配给ChildProperty
属性即可。但是,这需要将childProperty
更改为ParentItem
中的 var,因为需要重新分配它。或者,可以构造一个完整的ParentItem
。代码可能看起来像这样:
当然可以通过将
itemType
、childProperty
等细节注入到ItemDeserializer
中来概括整个事情。例如,但我更想展示基本方法。无论如何,要获得一个用于快速测试的独立示例,调用仍然丢失,可能如下所示:
然后调试控制台将输出以下内容,您可以看到反序列化代码给出了所需的结果:
One way would be to create a type adapter for the
ParentItem
class and within theJsonDeserializer
subclass based on the value of theitemType
property deserialize the child object with the correct class (ChildPropertyFoo
orChildPropertyBar
). Then you can simply assign the deserialized object to theChildProperty
property. However, this would requirechildProperty
to be changed to var inParentItem
since it needs to be reassigned. Alternatively, one could construct a completeParentItem
.The code might look something like this:
The whole thing could of course be generalized by injecting the details like
itemType
,childProperty
etc. into theItemDeserializer
instance, but I rather wanted to show the basic approach.Anyway, to get a self-contained example for a quick test the call is still missing, which could look like this:
The debug console will then output the following, you can see that the deserialization code gives the desired result: