如何使用 Retrofit2 和 GSON 从响应中获取特定的 JSON 数组?
How can I get a specific JSON Array from the response using Retrofit2 and GSON?
我正在尝试使用 Retrofit 从 JSON 格式中检索一些数据,并且只需要特定的数组形式的响应。我如何使用 GSON 做到这一点?例如,在下面的示例中,只需要 'articles' 数组。
status: "ok",
totalResults: 970843,
articles: [
{
...,
...,
...
},
您可以为这种特殊情况创建一个自定义反序列化器,或者(第二个选项是我将描述的)有一个更通用的解决方案,需要付出更多的努力,但会得到很好的回报长 运行,因为您可以将它应用于其他类似的用例。这是我最喜欢的方法,我在 GitHub 上遇到过它(我不是发明者) - 并且自己也采用了 - 你可以检查代码和项目以了解它们如何协同工作 - project link
下面是我摘录的相关片段:
- Create a special converter factory as such:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
/**
* A [Converter.Factory] which removes unwanted wrapping envelopes from API
* responses.
*/
class DenvelopingConverter(internal val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(
type: Type, annotations: Array<Annotation>?, retrofit: Retrofit?): Converter<ResponseBody, *>? {
// This converter requires an annotation providing the name of the payload in the envelope;
// if one is not supplied then return null to continue down the converter chain.
val payloadName = getPayloadName(annotations) ?: return null
val adapter = gson.getAdapter(TypeToken.get(type))
return Converter<ResponseBody, Any> { body ->
try {
val jsonReader = gson.newJsonReader(body.charStream())
jsonReader.beginObject()
while (jsonReader.hasNext()) {
if (payloadName == jsonReader.nextName()) {
return@Converter adapter.read(jsonReader)
} else {
jsonReader.skipValue()
}
}
return@Converter null
} finally {
body.close()
}
}
}
private fun getPayloadName(annotations: Array<Annotation>?): String? {
if (annotations == null) {
return null
}
for (annotation in annotations) {
if (annotation is EnvelopePayload) {
return annotation.value
}
}
return null
}
}
- Create this annotation class
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class EnvelopePayload(val value: String = "")
- In your retrofit builder, add the converter you created in step 1
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(DenvelopingConverter(gson))
- In your retrofit interface, use the @EnvelopePayload you created in step 2 to filter the elements you want, in your case, "articles", like so
interface MyService {
@EnvelopePayload("articles")
@GET("whatever")
//fun search....
}
就是这样,尽情享受吧!
我正在尝试使用 Retrofit 从 JSON 格式中检索一些数据,并且只需要特定的数组形式的响应。我如何使用 GSON 做到这一点?例如,在下面的示例中,只需要 'articles' 数组。
status: "ok",
totalResults: 970843,
articles: [
{
...,
...,
...
},
您可以为这种特殊情况创建一个自定义反序列化器,或者(第二个选项是我将描述的)有一个更通用的解决方案,需要付出更多的努力,但会得到很好的回报长 运行,因为您可以将它应用于其他类似的用例。这是我最喜欢的方法,我在 GitHub 上遇到过它(我不是发明者) - 并且自己也采用了 - 你可以检查代码和项目以了解它们如何协同工作 - project link
下面是我摘录的相关片段:
- Create a special converter factory as such:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
/**
* A [Converter.Factory] which removes unwanted wrapping envelopes from API
* responses.
*/
class DenvelopingConverter(internal val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(
type: Type, annotations: Array<Annotation>?, retrofit: Retrofit?): Converter<ResponseBody, *>? {
// This converter requires an annotation providing the name of the payload in the envelope;
// if one is not supplied then return null to continue down the converter chain.
val payloadName = getPayloadName(annotations) ?: return null
val adapter = gson.getAdapter(TypeToken.get(type))
return Converter<ResponseBody, Any> { body ->
try {
val jsonReader = gson.newJsonReader(body.charStream())
jsonReader.beginObject()
while (jsonReader.hasNext()) {
if (payloadName == jsonReader.nextName()) {
return@Converter adapter.read(jsonReader)
} else {
jsonReader.skipValue()
}
}
return@Converter null
} finally {
body.close()
}
}
}
private fun getPayloadName(annotations: Array<Annotation>?): String? {
if (annotations == null) {
return null
}
for (annotation in annotations) {
if (annotation is EnvelopePayload) {
return annotation.value
}
}
return null
}
}
- Create this annotation class
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class EnvelopePayload(val value: String = "")
- In your retrofit builder, add the converter you created in step 1
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(DenvelopingConverter(gson))
- In your retrofit interface, use the @EnvelopePayload you created in step 2 to filter the elements you want, in your case, "articles", like so
interface MyService {
@EnvelopePayload("articles")
@GET("whatever")
//fun search....
}
就是这样,尽情享受吧!