Gson根据父属性反序列化子对象

gcxthw6b  于 2022-11-06  发布在  其他
关注(0)|答案(2)|浏览(257)

我正在尝试反序列化一个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永远不会还原序列化,因为itemType存在于父对象中,所以无法推断型别,所以会维持null。
但是,如果我将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,所以我试图弄清楚如何创建类型适配器,以便它与父对象和子对象中定义的类型一起工作。

ki1q1bka

ki1q1bka1#

使用Gson,您可以通过实现一个自定义的TypeAdapterFactory来解决这个问题,该TypeAdapterFactory执行以下操作:
1.验证请求的类型是否为ParentItem
1.创建一个从itemType字符串到对应的TypeAdapter的Map,从Gson示例中获得(以下称为“itemTypeMap”)
1.从Gson示例中获取JsonObject的适配器(在下面称为“JsonObject适配器”)
1.从Gson示例中获取ParentItem的 * 委托适配器 *(以下称为“ParentItem适配器”)(需要 * 委托 * 适配器,因为否则Gson将简单地使用当前ParentItem工厂,导致无限递归)
1.创建并返回执行以下操作的适配器:
1.使用JsonObject适配器从读取器读取
1.从已解析的JsonObject中删除childProperty值,并将其存储在变量childPropertyValue
1.移除itemType值,并从itemTypeMap中取得Map的TypeAdapter(以下称为“子配接卡”)
1.在已解析的JsonObject上使用ParentItem适配器(没有childPropertyValue; Gson不会抱怨丢失的财产)
1.在childPropertyValue上使用子适配器,并将其结果存储在先前读取的ParentItem对象的childProperty中(这需要将ParentItem.childProperty设置为var
1.返回ParentItem对象
然后,您只需要将该TypeAdapterFactory注册到GsonBuilder(以及可选的ChildPropertyFooChildPropertyBar的任何自定义适配器)。
下面是TypeAdapterFactory的示例实现:

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

rn0zuynd

rn0zuynd2#

一种方法是为ParentItem类创建一个类型适配器,并在JsonDeserializer子类中基于itemType属性的值使用正确的类反序列化子对象(ChildPropertyFooChildPropertyBar)。然后,您可以简单地将反序列化的对象赋给ChildProperty属性。但是,这将需要在ParentItem中将childProperty更改为var,因为它需要重新分配。
代码可能如下所示:

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示例来概括整个过程,但我更希望展示基本方法。
无论如何,要获得一个用于快速测试的自包含示例,调用仍然缺失,可能如下所示:

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)
            }
        }
    }
}

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

This property will be here if itemType is 'foo'
def
This property will be here if itemType is 'bar'
jkl

相关问题