0

我正在开发 Android 应用程序,并且正在使用 Spring Rest Data 编写后端。

设想:

假设我想列出用户最喜欢的产品。我需要GET /v1/users/4/favoriteProducts在标题中调用提供访问令牌。如果用户有最喜爱的产品,我会得到如下响应:

{
    "links" : [ {
        "rel" : "self",
        "href" : "BASE_URL/v1/users/4/favoriteProducts",
        "hreflang" : null,
        "media" : null,
        "title" : null,
        "type" : null,
        "deprecation" : null
    } ],
    "content" : [ 
        {PRODUCT},
        {PRODUCT},
        {PRODUCT},
    ]
}

我可以毫无问题地反序列化它,我得到HalList包含三个产品的对象。我的改造服务调用如下所示:

@GET("v1/users/{user}/favoriteProducts")
fun favoriteProducts(@Path("user") userId: Int): Call<HalList<Product>>

和我的HalList班级:

data class HalList<T>(
    @SerializedName("links")
    val links: List<HalLink>,

    @SerializedName("content")
    var content: List<T>
)

但如果我没有任何最喜欢的产品,我会得到这样的东西:

{
    "links" : [ {
        "rel" : "self",
        "href" : "BASE_URL/v1/users/4/favoriteProducts",
        "hreflang" : null,
        "media" : null,
        "title" : null,
        "type" : null,
        "deprecation" : null
    } ],
   "content" : [ {
       "rel" : null,
       "collectionValue" : true,
       "relTargetType" : "com.example.entity.Product",
       "value" : [ ]
   } ]
}

当我用 Retrofit 解析这个对象时,我得到的HalList对象content包含一个 Product 类实例,所有值都设置为 0 或 null 或 false,具体取决于类型,这会产生问题..

我已经TypeAdapter为 Gson 编写了这个,以便在基于 json 的空列表为空的情况下用空列表覆盖内容

class HalTypeAdapterFactory : TypeAdapterFactory {
    override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T> {
        val delegate = gson?.getDelegateAdapter(this, type)!!

        if (!HalReflection.isResource(type?.rawType!!)) {
            return delegate
        }

        return HalTypeAdapter(gson, type, delegate)
    }
}

class HalTypeAdapter<T>(private val gson: Gson, private val type: TypeToken<T>, private val delegate: TypeAdapter<T>) : TypeAdapter<T>() {
    private val basicAdapter = gson.getAdapter(JsonElement::class.java)!!

    override fun write(out: JsonWriter?, value: T) {
        delegate.write(out, value)
    }

    override fun read(`in`: JsonReader?): T {
        val fullJson = basicAdapter.read(`in`)
        val deserialized = delegate.fromJsonTree(fullJson)

        if(type.rawType == HalList::class.java || type.rawType == HalPagedList::class.java) {
            if(fullJson.isJsonObject) {
                val content = fullJson.asJsonObject.getAsJsonArray("content")
                if(content[0].isJsonObject) {
                    val o = content[0].asJsonObject
                    if(o.has("collectionValue") && o.has("relTargetType")) {
                        val field = type.rawType.getDeclaredField("content")
                        field.isAccessible = true
                        field.set(deserialized, listOf<T>())
                        field.isAccessible = false
                    }
                }
            }
        }

        return deserialized
    }
}

但我不确定这是否是正确的解决方案,还有更优雅的解决方案吗?还有什么可以在后端完成以返回这样的空列表:

{
    "links" : [ {
        "rel" : "self",
        "href" : "BASE_URL/v1/users/4/favoriteProducts",
        "hreflang" : null,
        "media" : null,
        "title" : null,
        "type" : null,
        "deprecation" : null
    } ],
   "content" : []
}

编辑:最终解决方案

我不喜欢让 Gson 的委托适配器解析响应然后检查 json 字符串并使用反射将其更改为空列表的想法。在反序列化之前,我改变了修改 json 响应的方法:

class HalTypeAdapterFactory : TypeAdapterFactory {
    override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T> {
        val delegate = gson?.getDelegateAdapter(this, type)!!

        if (type?.rawType == HalList::class.java)
            return HalListTypeAdapter(gson, type, delegate)

        return delegate
    }
}

class HalListTypeAdapter<T>(
        private val gson: Gson,
        private val type: TypeToken<T>?,
        private val delegate: TypeAdapter<T>)
    : TypeAdapter<T>() {

    private val basicAdapter = gson.getAdapter(JsonElement::class.java)

    override fun write(out: JsonWriter?, value: T) {
        delegate.write(out, value)
    }

    override fun read(`in`: JsonReader?): T {
        val json = basicAdapter.read(`in`)

        if (json.isJsonObject) {
            val resource = json.asJsonObject
            val content = resource.getAsJsonArray("content")
            if (isEmptyCollection(content)) {
                resource.remove("content")
                resource.add("content", JsonArray())
            }
        }

        return delegate.fromJsonTree(json)
    }

    private fun isEmptyCollection(content: JsonArray): Boolean {
        if (content.size() == 1 && content[0].isJsonObject) {
            val first = content[0].asJsonObject
            return first.has("collectionValue") && first.has("relTargetType")
        }

        return false
    }
}

data class HalList<T>(
        @SerializedName("links")
        val links: List<HalReference>,

        @SerializedName("page")
        val page: HalListPageMeta?,

        @SerializedName("content")
        val content: List<T>
)
4

0 回答 0