【问题标题】:Making Gson work for interface type determined by outer field使 Gson 适用于由外部字段确定的接口类型
【发布时间】:2019-01-20 21:12:48
【问题描述】:

问题类似于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
}

如果typeABaz 如果typeB,我如何使myObject 映射到Bar

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

编辑:

当尝试使用 Gson fromJson 方法将根 JSON 对象映射到 Blah 时,我收到此错误:Unable to invoke no-args constructor for class Foo。 - 但这无论如何都是不正确的,因为我需要 myObject 专门映射到 BazBar

【问题讨论】:

    标签: java android kotlin gson deserialization


    【解决方案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
    

    【讨论】:

    • 谢谢,但我避免这样做,因为这意味着我必须手动映射 Blah 的所有字段,因为没有映射一个字段。我一直在寻找更半自动化的东西。我现在有一个解决方案,将在今天晚些时候发布。
    • 我已经发布了我的答案。再次感谢。
    【解决方案2】:

    这是我的解决方案。解决方案在 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

    【讨论】:

      猜你喜欢
      • 2022-11-16
      • 2011-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多