我正在开发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,这会引起问题..
我为Gson编写了此TypeAdapter
,以便在基于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>
)