Заставить Gson работать для типа интерфейса, определяемого внешним полем - PullRequest
0 голосов
/ 21 января 2019

Вопрос похож на этот но отличается тем, что 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
}

Как мне сделать myObject отображением на Bar, если type равно A и Baz, если type равно B?

Временно я прибегнул к чтению в корневом объекте JSON вручную. Любая помощь приветствуется. Спасибо.

EDIT:

При попытке сопоставить корневой объект JSON с Blah с использованием метода Gson fromJson, я получаю эту ошибку: Unable to invoke no-args constructor for class Foo. - Но в любом случае это неверно, потому что мне нужно myObject для точного сопоставления с Baz или Bar.

Ответы [ 2 ]

0 голосов
/ 22 января 2019

Вот мое решение для этого.Решение в 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'

Вдохновение для этого решения пришло от этой статьи

0 голосов
/ 21 января 2019

Это очень похоже на вопрос, который вы упомянули.

Вы можете определить десериализатор для 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
...