使Gson适用于由外场确定的接口类型

时间:2019-01-20 21:12:48

标签: java android kotlin gson deserialization

问题类似于this 但不同之处在于type不包含在要反序列化的JSON对象中,而是包含在外部层(根)中。

像这样:

{
    "type": "A",
    "myObject" : { <- type is NOT stored in here
        ...
    }
}

目的是将此JSON对象映射到Blah

// Code is in Kotlin

interface Foo {
    ...
}

class Bar : Foo { // Map to this if 'type' is A
    ...
}

class Baz : Foo { // Map to this if 'type' is B
    ...
}

class Blah {

    val type : String? = null
    val myObject : Foo? = null
}

如果myObjectBar,如何将type映射到A,如果Baztype,如何使B映射到Blah

暂时,我不得不手动读取JSON根对象。任何帮助将非常感激。谢谢。

编辑:

当尝试使用Gson fromJson方法将根JSON对象映射到Unable to invoke no-args constructor for class Foo时,出现此错误:myObject。 -但这仍然是不正确的,因为我需要Baz才能专门映射到BarCaman('#canvas', function () { this.colorize(25, 180, 200, 20); this.render(); });

2 个答案:

答案 0 :(得分:1)

这与您提到的问题非常相似。

您可以为Blah定义一个反序列化器,并确定应使用哪个类。

代码如下所示。

import com.google.gson.*

interface Foo {
    fun bark()
}

class Bar : Foo { // Map to this if 'type' is A
    override fun bark() {
        print("bar")
    }
}

class Baz : Foo { // Map to this if 'type' is B
    override fun bark() {
        print("baz")
    }
}

class Blah(val type : String? = null, val myObject : Foo? = null) {
    companion object {
        const val TYPE_A = "A"
        const val TYPE_B = "B"
    }
}

class BlahJsonDeserializer: JsonDeserializer<Blah> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Blah {
        val root = json?.asJsonObject
        val type = root?.get("type")?.asString
        var obj: Foo? = null
        when(type ?: "") {
            Blah.TYPE_A -> { obj = Bar() }
            Blah.TYPE_B -> { obj = Baz() }
        }
        val blah = Blah(type, obj)
        return blah
    }
}

val json = "{'type': 'A', 'myObject': {}}"

val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeAdapter(Blah::class.java, BlahJsonDeserializer())
val gson = gsonBuilder.create()
val item = gson.fromJson<Blah>(json, Blah::class.java)

item.myObject?.bark() // bar

答案 1 :(得分:0)

这是我的解决方案。解决方案在Kotlin。

编辑课程Blah

class Blah {

    val type : String? = null

    @ExcludeOnDeserialization // <- add this
    val myObject : Foo? = null
}

在一些静态类中:

inline fun <T : Annotation>findAnnotatedFields(
    annotation: Class<T>,
    clazz : Class<*>,
    onFind : (Field) -> Unit
){

    for(field in clazz.declaredFields){

        if(field.getAnnotation(annotation)!=null){

            field.isAccessible = true

            onFind(field)
        }
    }
}

创建新的类和注释:

@Target(AnnotationTarget.FIELD)
annotation class ExcludeOnDeserialization

class GsonExclusionStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>?): Boolean {
        return clazz?.getAnnotation(ExcludeOnDeserialization::class.java) != null
    }

    override fun shouldSkipField(f: FieldAttributes?): Boolean {
        return f?.getAnnotation(ExcludeOnDeserialization::class.java) != null
    }
}

读取json根对象:

...

val gson = GsonBuilder()
    .addDeserializationExclusionStrategy(GsonExclusionStrategy())
    .create()

val rootJsonObject = JsonParser().parse(rootJsonObjectAsString)
val blah = gson.fromJson(rootJsonObject, Blah::class.java)

findAnnotatedFields(
    ExcludeOnDeserialization::class.java,
    Blah::class.java
){ foundExcludedField -> // foundExcludedField = 'myObject' declared in 'Blah' class

    val myObjectAsJsonObject
        = rootJsonObject.asJsonObject.getAsJsonObject(foundExcludedField.name)

    when (foundExcludedField.type) {

        Foo::class.java -> {

            when (blah.type) {

                "A" -> {

                    foundExcludedField.set(
                        blah,
                        gson.fromJson(myObjectAsJsonObject, Bar::class.java)
                    )
                }

                "B" -> {

                    foundExcludedField.set(
                        blah,
                        gson.fromJson(myObjectAsJsonObject, Baz::class.java)
                    )
                }

                else -> return null
            }
        }
    }
}

// The root json object has now fully been mapped into 'blah'

此解决方案的灵感来自this article