如何在转换为类型化实体之前更改 JSON 响应的根?

问题描述 投票:0回答:1

任务

远程服务器返回带有正文的响应:

{
    done: true,
    response: {
        result: {
            // data
        }
    }
}

如果出现错误,带有

200
状态和正文的响应:

{
    done: false,
    error: {
        message: ""
    }
}

在将 JSON 传输到类型化实体之前,我想将

result
数据设置为响应的根。

{
    // data
}

我无法为Retrofit接口的所有功能创建一个包装模型(这是一个很长的故事)。

我的解决方案

我创建了一个自定义工厂

ProxyGsonConverterFactory
(基于GsonConverterFactory.java)。它按预期工作。

GsonResponseBodyConverter
课程。为了更改 json 根,该类使用
org.json.JSONObject
。它将字符串解析为对象并获得
resultData
。然后将
resultData
转换回字符串并使用
gson
将其转换为通用
T

class ProxyGsonConverterFactory (
    private val gson: Gson
) : Converter.Factory() {

    override fun responseBodyConverter(
        type: Type, annotations: Array<Annotation>, retrofit: Retrofit
    ): Converter<ResponseBody, *> {
        val adapter = gson.getAdapter(TypeToken.get(type))
        return GsonResponseBodyConverter(gson, adapter)
    }

    override fun requestBodyConverter(
        type: Type,
        parameterAnnotations: Array<Annotation>,
        methodAnnotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<*, RequestBody> {
        val adapter = gson.getAdapter(TypeToken.get(type))
        return GsonRequestBodyConverter(gson, adapter)
    }
}

internal class GsonResponseBodyConverter<T>(
    private val gson: Gson,
    private val adapter: TypeAdapter<T>
) : Converter<ResponseBody, T> {

    @Throws(IOException::class)
    override fun convert(value: ResponseBody): T {
        val jsonObject = JSONObject(value.string())
        val jsonPayload = jsonObject.optJSONObject("response")?.opt("result")
        if (jsonPayload == null) {
            val errorMessage = jsonObject.optJSONObject("error")?.toString()
            throw ProxyExecutionException(errorMessage ?: "GsonResponseBodyConverter: payload is null")
        }
        
        val modifiedJsonStr = jsonPayload.toString()
        val jsonReader = gson.newJsonReader(modifiedJsonStr.reader())
        val result = adapter.read(jsonReader)
        if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
            throw JsonIOException("JSON document was not fully consumed.")
        }
        jsonReader.close()
        return result
    }
}


internal class GsonRequestBodyConverter<T>(
    private val gson: Gson,
    private val adapter: TypeAdapter<T>
) : Converter<T, RequestBody> {

    @Throws(IOException::class)
    override fun convert(value: T): RequestBody {
        val buffer = Buffer()
        val writer = OutputStreamWriter(buffer.outputStream(), StandardCharsets.UTF_8)
        val jsonWriter = gson.newJsonWriter(writer)
        adapter.write(jsonWriter, value)
        jsonWriter.close()
        return buffer.readByteString().toRequestBody(MEDIA_TYPE)
    }

    companion object {
        private val MEDIA_TYPE: MediaType = "application/json; charset=UTF-8".toMediaType()
    }
}

问题

GsonResponseBodyConverter
解析 JSON 字符串两次。使用
org.json.JSONObject
需要更多时间。我可以只用一次解析来解决任务吗?

例如,函数

ProxyGsonConverterFactory.responseBodyConverter
包含泛型T的
Type
来创建
gson
适配器。我们可以将 T 的适配器放入父适配器
ResponseWrapper
,以便解析一次然后仅返回 T 主体吗?

data class ResponseWrapper <T>(
    val done: boolean
    val response: ResponseBody<T>
)

data class ResponseBody <T>(
    val result: T
)
android kotlin gson retrofit
1个回答
0
投票

除了将

result
数据移动到根目录之外,您还可以尝试 Kotlin 扩展。

data class ResponseWrapper<T>(
    val done: Boolean,
    val response: ResponseBody<T>?,
    val error: JsonElement?
)

data class ResponseBody<T>(
    val result: T
)

// ResponseWrapper extension for result
val <T> ResponseWrapper<T>.result: T? get() = this.response?.result

如果没有找到 JSON 键,Gson 会自动将

null
设置为可为 null 的变量。

您可以从这样的回复中轻松获得

result

val apiResponse = api.test()
val isDone = apiResponse.done
val result = apiResponse.result
© www.soinside.com 2019 - 2024. All rights reserved.