首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于父属性的Gson反序列化子对象

基于父属性的Gson反序列化子对象
EN

Stack Overflow用户
提问于 2022-02-28 13:29:25
回答 2查看 933关注 0票数 0

我正在尝试反序列化包含对象的json响应,这些对象具有可以根据父类中的属性更改类型的子对象。我已经看过如何使用类型适配器工厂来反序列化定义了自己的属性类型的子对象的示例,但是无法知道如何在父对象中定义类型。这个是可能的吗?

示例JSON

代码语言:javascript
复制
{
    "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反序列化为不同的类型。

给定下面用于序列化的类:

代码语言:javascript
复制
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()

类型适配器为:

代码语言:javascript
复制
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从不反序列化--它仍然是空的,因为它不能推断类型,因为itemType驻留在父对象中。

但是,如果我将json模式更改为itemType位于子对象中的下面的位置,则一切都可以反序列化。

代码语言:javascript
复制
{
    "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,因此我正在尝试如何创建类型适配器,以便它与父对象和子对象中定义的类型一起工作。

EN

回答 2

Stack Overflow用户

发布于 2022-02-28 21:14:15

使用Gson,您可以通过实现一个自定义TypeAdapterFactory来解决这个问题,它执行以下操作:

验证请求的类型是ParentItem

  • Create --一个从itemType字符串到相应TypeAdapter的映射,从Gson实例(在下面称为"itemType映射“)获得

  • Gson实例获取JsonObject适配器(在下面的名称为"JsonObject

  • a ParentItem adapter >(需要委托适配器),因为否则Gson将简单地使用当前ParentItem工厂,导致无限recursion)

  • Create并返回一个适配器,该适配器执行以下操作:
  1. 使用JsonObject适配器从解析的JsonObject中读取childProperty值,并将其存储在变量childPropertyValue
  2. Remove中,即itemType值,并从itemType映射中获得相应的TypeAdapter (在下面的名称为“在解析的JsonObject上的子adapter")
  3. Use,ParentItem适配器(没有childPropertyValue);Gson不会抱怨缺少的property)
  4. Use,即childPropertyValue上的子适配器,并将其结果存储在以前读取的ParentItem对象的childProperty中(这需要使ParentItem.childProperty成为ParentItem object

)。

然后您只需要register that TypeAdapterFactory with a GsonBuilder (也可以选择ChildPropertyFooChildPropertyBar的任何自定义适配器)。

下面是TypeAdapterFactory的一个示例实现

代码语言:javascript
复制
object ParentItemTypeAdapterFactory : TypeAdapterFactory {
    override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        // Only support ParentItem and subtypes
        if (!ParentItem::class.java.isAssignableFrom(type.rawType)) {
            return null
        }

        // Safe cast due to check at beginning of function
        @Suppress("UNCHECKED_CAST")
        val delegateAdapter = gson.getDelegateAdapter(this, type) as TypeAdapter<ParentItem>
        val jsonObjectAdapter = gson.getAdapter(JsonObject::class.java)
        val itemTypeMap = mapOf(
            "foo" to gson.getAdapter(ChildPropertyFoo::class.java),
            "bar" to gson.getAdapter(ChildPropertyBar::class.java),
        )

        // Safe cast due to check at beginning of function
        @Suppress("UNCHECKED_CAST")
        return object : TypeAdapter<ParentItem>() {
            override fun read(reader: JsonReader): ParentItem? {
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull()
                    return null
                }

                val parentItemValue = jsonObjectAdapter.read(reader)
                val itemType = parentItemValue.remove("itemType").asString
                val childAdapter = itemTypeMap[itemType]
                    ?: throw JsonParseException("Invalid item type: $itemType")
                val childPropertyValue = parentItemValue.remove("childProperty")

                val itemObject = delegateAdapter.fromJsonTree(parentItemValue)
                val childObject = childAdapter.fromJsonTree(childPropertyValue)
                itemObject.childProperty = childObject

                return itemObject
            }

            override fun write(writer: JsonWriter, value: ParentItem?) {
                throw UnsupportedOperationException()
            }
        } as TypeAdapter<T>
    }
}

请注意,其他JSON框架提供了这种现成的功能,例如Jackson有JsonTypeInfo.As.EXTERNAL_PROPERTY

票数 0
EN

Stack Overflow用户

发布于 2022-02-28 21:33:31

一种方法是根据ParentItem属性的值为JsonDeserializer类和JsonDeserializer子类创建一个类型适配器,用正确的类(ChildPropertyFooChildPropertyBar)反序列化子对象。然后,您可以简单地将反序列化对象分配给ChildProperty属性。但是,这需要将childProperty更改为ParentItem中的var,因为它需要重新分配。或者,我们可以构建一个完整的ParentItem

代码可能如下所示:

代码语言:javascript
复制
import com.google.gson.*
import java.lang.reflect.Type


internal class ItemDeserializer : JsonDeserializer<ParentItem> {

    override fun deserialize(
        json: JsonElement,
        t: Type,
        jsonDeserializationContext: JsonDeserializationContext
    )
            : ParentItem? {
        val type = (json as JsonObject)["itemType"].asString
        val gson = Gson()
        val childJson = json["childProperty"]
        val childClass = if (type == "foo") ChildPropertyFoo::class.java else ChildPropertyBar::class.java
        val childObject = gson.fromJson<ChildProperty>(childJson, childClass)
        val parent = gson.fromJson(json, ParentItem::class.java) as ParentItem
        parent.childProperty = childObject
        return parent
    }

}

当然,通过将诸如itemTypechildProperty等细节注入到ItemDeserializer实例中,可以对整个过程进行概括,但我更希望展示基本的方法。

无论如何,要获得一个快速测试的自包含示例,仍然缺少调用,它可能如下所示:

代码语言:javascript
复制
import com.google.gson.GsonBuilder

fun main() {
    val deserializer = ItemDeserializer()
    val gson = GsonBuilder().registerTypeAdapter(ParentItem::class.java, deserializer).create()
    val deserializedTest = gson.fromJson(json, FooBarWrapper::class.java)
    for (item in deserializedTest.items) {
        when (val childProperty = item.childProperty) {
            is ChildPropertyFoo -> {
                println(childProperty.foo)
                println(childProperty.abc)
            }
            is ChildPropertyBar -> {
                println(childProperty.bar)
                println(childProperty.ghi)
            }
        }
    }
}

然后调试控制台将输出以下内容,您可以看到反序列化代码提供了所需的结果:

代码语言:javascript
复制
This property will be here if itemType is 'foo'
def
This property will be here if itemType is 'bar'
jkl
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71295744

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档