可选+可空值的Gson反序列化

Gson deserialization of optional+nullable value

我正在尝试解决的问题已由来自 this link 的以下文本完美描述:

For a concrete example of when this could be useful, consider an API that supports partial updates of objects. Using this API, a JSON object would be used to communicate a patch for some long-lived object. Any included property specifies that the corresponding value of the object should be updated, while the values for any omitted properties should remain unchanged. If any of the object’s properties are nullable, then a value of null being sent for a property is fundamentally different than a property that is missing, so these cases must be distinguished.

post 提供了一个解决方案,但使用 kotlinx.serialization 库,但是,我现在必须使用 gson 库。

所以我正在尝试实施我自己的解决方案,因为我没有找到任何适合我的用例的东西(如果有请告诉我)。

data class MyObject(
    val fieldOne: OptionalProperty<String> = OptionalProperty.NotPresent,
    val fieldTwo: OptionalProperty<String?> = OptionalProperty.NotPresent,
    val fieldThree: OptionalProperty<Int> = OptionalProperty.NotPresent
)

fun main() {
    val gson = GsonBuilder()
        .registerTypeHierarchyAdapter(OptionalProperty::class.java, OptionalPropertyDeserializer())
        .create()
    val json1 = """{
            "fieldOne": "some string",
            "fieldTwo": "another string",
            "fieldThree": 18
        }
        """
    println("json1 result object: ${gson.fromJson(json1, MyObject::class.java)}")
    val json2 = """{
            "fieldOne": "some string",
            "fieldThree": 18
        }
        """
    println("json2 result object: ${gson.fromJson(json2, MyObject::class.java)}")
    val json3 = """{
            "fieldOne": "some string",
            "fieldTwo": null,
            "fieldThree": 18
        }
        """
    println("json3 result object: ${gson.fromJson(json3, MyObject::class.java)}")

}

sealed class OptionalProperty<out T> {

    object NotPresent : OptionalProperty<Nothing>()

    data class Present<T>(val value: T) : OptionalProperty<T>()
}

class OptionalPropertyDeserializer : JsonDeserializer<OptionalProperty<*>> {

    private val gson: Gson = Gson()

    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): OptionalProperty<*> {
        println("Inside OptionalPropertyDeserializer.deserialize json:$json")
        return when {
            // Is it a JsonObject? Bingo!
            json?.isJsonObject == true ||
                    json?.isJsonPrimitive == true-> {

                // Let's try to extract the type in order
                // to deserialize this object
                val parameterizedType = typeOfT as ParameterizedType

                // Returns an Present with the value deserialized
                return OptionalProperty.Present(
                    context?.deserialize<Any>(
                        json,
                        parameterizedType.actualTypeArguments[0]
                    )!!
                )
            }
            // Wow, is it an array of objects?
            json?.isJsonArray == true -> {

                // First, let's try to get the array type
                val parameterizedType = typeOfT as ParameterizedType

                // check if the array contains a generic type too,
                // for example, List<Result<T, E>>
                if (parameterizedType.actualTypeArguments[0] is WildcardType) {

                    // In case of yes, let's try to get the type from the
                    // wildcard type (*)
                    val internalListType = (parameterizedType.actualTypeArguments[0] as WildcardType).upperBounds[0] as ParameterizedType

                    // Deserialize the array with the base type Any
                    // It will give us an array full of linkedTreeMaps (the json)
                    val arr = context?.deserialize<Any>(json, parameterizedType.actualTypeArguments[0]) as ArrayList<*>

                    // Iterate the array and
                    // this time, try to deserialize each member with the discovered
                    // wildcard type and create new array with these values
                    val result = arr.map { linkedTreeMap ->
                        val jsonElement = gson.toJsonTree(linkedTreeMap as LinkedTreeMap<*, *>).asJsonObject
                        return@map context.deserialize<Any>(jsonElement, internalListType.actualTypeArguments[0])
                    }

                    // Return the result inside the Ok state
                    return OptionalProperty.Present(result)
                } else {
                    // Fortunately it is a simple list, like Array<String>
                    // Just get the type as with a JsonObject and return an Ok
                    return OptionalProperty.Present(
                        context?.deserialize<Any>(
                            json,
                            parameterizedType.actualTypeArguments[0]
                        )!!
                    )
                }
            }
            // It is not a JsonObject or JsonArray
            // Let's returns the default state NotPresent.
            else -> OptionalProperty.NotPresent
        }
    }

}

我从 here.

获得了自定义解串器的大部分代码

这是我 运行 main 函数时的输出:

Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:"another string"
Inside OptionalPropertyDeserializer.deserialize json:18
json1 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=Present(value=another string), fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json2 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=my.package.OptionalProperty$NotPresent@573fd745, fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json3 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=null, fieldThree=Present(value=18))

我正在测试 fieldTwo 的不同选项,它几乎完全正常工作,除了第 3 个 json,我希望 fieldTwo 应该是 fieldTwo=Present(value=null)而不是 fieldTwo=null.

而且我看到在这种情况下,甚至没有为 fieldTwo 调用自定义解串器。

谁能看出我在这里遗漏了什么?任何提示将不胜感激!

我放弃了 gson,转向了 moshi。

我根据 this comment 中提供的解决方案实现了此行为。