You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Android中使用Retrofit和Kotlin解析无键JSON数组的问题及解决

在Android中使用Retrofit和Kotlin解析无键JSON数组的问题及解决

我最近在开发Android应用时,被API调用和响应解析的问题难住了——原本能正常序列化/Parcelize的API响应格式突然变了,我一时找不到合适的解析方法,折腾了好久才搞定,现在把过程分享出来。

问题背景:API响应格式变更

之前的API返回的是带键的JSON对象数组,每个对象都有明确的字段名,比如:

[
  {
    "msg": "No data available for this time period: 2022-11-14 11:00:00 and 2022-11-14 11:15:00"
  },
  {
    "datetime": "2022-11-14 11:15:00",
    "avg_temp": 27.59265899658203,
    "min_temp": 27.59265899658203,
    "max_temp": 27.59265899658203,
    "open_temp": 27.59265899658203,
    "close_temp": 27.59265899658203
  }
]

但后来API返回格式改成了无键的二维数组,每个子数组的元素对应原来的字段值,顺序是固定的:

[
  [
    1668413700,
    -99,
    29,
    27.59,
    27.59,
    27.59,
    27.59,
    27.59
  ],
  [
    1668414600,
    -99,
    29,
    27.59,
    27.59,
    27.59,
    27.59,
    27.59
  ]
]

最初的实现尝试

我之前已经定义了带@Parcelize和@SerializedName注解的数据类,用来解析旧的响应格式:

@Parcelize
data class TRA(
    @SerializedName("datetime")
    val datetime: Long?,
    @SerializedName("pred")
    val pred: Float?,
    @SerializedName("sensor")
    val sensor: Int?,
    @SerializedName("average")
    val average: Float?,
    @SerializedName("min")
    val min: Float?,
    @SerializedName("max")
    val max: Float?,
    @SerializedName("open")
    val open: Float?,
    @SerializedName("close")
    val close: Float?,
    @SerializedName("msg")
    val msg: String?
): Parcelable

然后在Repository里尝试手动解析新的响应,用JSONArray循环处理每个子数组,映射到TRA对象:

suspend fun uploadId(request: TextRecognitionRequest): List<TRA>? {
    return try {
        val response = api.uploadSensorId(request = request)
        val jsonArray = JSONArray(response)
        val resultList = mutableListOf<TRA>()
        for (i in 0 until jsonArray.length()) {
            val json = jsonArray.getJSONArray(i)
            val item = TRA(
                datetime = json.optInt(0).toLong(),
                pred = json.optDouble(1).toFloat(),
                sensor = json.optInt(2),
                average = json.optDouble(3).toFloat(),
                min = json.optDouble(4).toFloat(),
                max = json.optDouble(5).toFloat(),
                open = json.optDouble(6).toFloat(),
                close = json.optDouble(7).toFloat(),
            )
            resultList.add(item)
        }
        resultList
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

ViewModel和MainActivity里的观测逻辑是这样的:

@HiltViewModel
class MainActivityViewModel @Inject constructor(
    private val repository: CameraRepository
) : ViewModel() {

    private val _sensorResponse = MutableLiveData<List<TRA>>()
    val sensorResponse: LiveData<List<TRA>> = _sensorResponse

    fun uploadSensorId(request: TextRecognitionRequest) {
        viewModelScope.launch {
            _sensorResponse.postValue(repository.uploadSensorId(request))
        }
    }
}
private fun lineChartForDataObservation() {
    viewModel.sensorResponse.observe(this) { response ->
        response?.let { safeResponse ->
            val values = arrayListOf<Float>()
            val valuesPred = arrayListOf<Float>()
            val valuesClose = arrayListOf<Float>()
            val valuesOpen = arrayListOf<Float>()
            val valuesMax = arrayListOf<Float>()
            val valuesMin = arrayListOf<Float>()
            dates = arrayListOf()
            val sensors = arrayListOf<Int>()

            for (item in safeResponse) {
                item.datetime?.let { dates.add(it.toString()) }
                item.pred?.let { valuesPred.add(it)  }
                item.average?.let { values.add(it) }
                item.open?.let { valuesOpen.add(it) }
                item.close?.let { valuesClose.add(it) }
                item.min?.let { valuesMin.add(it) }
                item.max?.let { valuesMax.add(it) }
            }

            chartModels.add(
                AASeriesElement()
                    .name(sensors.component1().toString())
                    .data(values.toTypedArray())
                    .allowPointSelect(true)
                    .dashStyle(AAChartLineDashStyleType.Solid)
            )
            drawChart()
        } ?: run {
            Toast.makeText(this, "Something went wrong!", Toast.LENGTH_LONG).show()
            startActivity(Intent(this, OpenCameraActivity::class.java))
        }
    }
}

Retrofit的配置是用Gson转换器:

@Provides
@Singleton
fun provideCameraApi(okHttpClient: OkHttpClient): CameraApi {
    return Retrofit.Builder()
        .baseUrl(Constants.BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(CameraApi::class.java)
}

解决方法

折腾了半天后发现,问题出在Retrofit接口的返回类型定义上!原来的接口可能定义成了返回List<TRA>或者其他对象类型,但新的响应是二维数组,所以需要把接口的返回类型改成Array<Array<Double>>,这样Retrofit能正确解析原始的数组响应,之后再手动映射到TRA对象。

修改后的API接口示例:

@POST("your-api-endpoint")
suspend fun uploadSensorId(
    @Header("Accept") header1: String = "your-header-value",
    @Header("User-Agent") header2: String = "your-header-value",
    @Header("Connection") header5: String= "your-header-value",
    @Body request: TextRecognitionRequest,
) : Array<Array<Double>>

这样修改后,Repository里的解析逻辑就能正常拿到二维数组,再循环把每个子数组的元素对应到TRA的字段上,整个流程就跑通了!

备注:内容来源于stack exchange,提问作者Kaljal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 14:33:05