具有重复字段的 Moshi

Moshi with duplicate fields

我收到的 json 响应在某些情况下看起来像这样:

{
      "id" : 12345,
      "events": [
         {
            "desc": "Bla bla"
             ...
         }, 
         {
            "desc": "Yada yada",
            ...
         },
       ]

    }

而对于其他一些场景,它看起来像这样:

{
  "id" : 12345,
  "events": {
     "desc": "Bla bla"
     ...
  },
  "events" : {
    "desc": "Yada yada"
    ...
  },
}

也就是说,有时events会是一个数组,有时events会重复多个值。这会使用 moshi + retrofit 抛出以下异常:

    2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp: Multiple values for 'events' at $[0].events
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp: com.squareup.moshi.JsonDataException: Multiple values for 'events' at $[0].events
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at com.squareup.moshi.kotlin.reflect.KotlinJsonAdapter.fromJson(KotlinJsonAdapter.kt:80)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:40)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at com.squareup.moshi.CollectionJsonAdapter.fromJson(CollectionJsonAdapter.java:76)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at com.squareup.moshi.CollectionJsonAdapter.fromJson(CollectionJsonAdapter.java:53)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:40)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:45)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:27)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
2019-12-30 13:58:20.458 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:45)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Observable.subscribe(Observable.java:11194)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Observable.subscribe(Observable.java:11194)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.observable.ObservableSingleSingle.subscribeActual(ObservableSingleSingle.java:35)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleFlatMap$SingleFlatMapCallback.onSuccess(SingleFlatMap.java:84)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleDoOnSuccess$DoOnSuccess.onSuccess(SingleDoOnSuccess.java:59)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:56)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleDoOnSuccess.subscribeActual(SingleDoOnSuccess.java:35)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.459 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleMap.subscribeActual(SingleMap.java:34)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleDoOnSuccess.subscribeActual(SingleDoOnSuccess.java:35)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Single.subscribe(Single.java:3096)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.single.SingleToFlowable.subscribeActual(SingleToFlowable.java:37)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13234)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13180)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.flowable.FlowableZip$ZipCoordinator.subscribe(FlowableZip.java:127)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.flowable.FlowableZip.subscribeActual(FlowableZip.java:79)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13234)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:38)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13234)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.flowable.FlowableOnErrorReturn.subscribeActual(FlowableOnErrorReturn.java:33)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13234)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.Flowable.subscribe(Flowable.java:13180)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
2019-12-30 13:58:20.460 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
2019-12-30 13:58:20.461 21419-21419/com.myapp.android.debug E/DetailFragment$loadUp:     at java.lang.Thread.run(Thread.java:764)

我想对这两种情况的输出进行标准化,也就是说,我想把它变成

data class Parcel(val events: List<Event>)

我知道第二种响应格式不正确,但我无法控制后端(它是我正在使用的外部服务),有没有办法解决这个问题?

我尝试摆弄自定义适配器,但我无法弄清楚如何去做:(

编辑:我对自定义适配器的最佳尝试:

class CorreosApiParcelAdapter(private val eventAdapter: JsonAdapter<CorreosApiEvent>,
                              private val errorAdapter: JsonAdapter<Error>) : JsonAdapter<CorreosApiParcel>() {
    override fun fromJson(reader: JsonReader): CorreosApiParcel? = with(reader) {
        val events = mutableListOf<CorreosApiEvent>()
        val parcel = CorreosApiParcel.allNull()
        beginObject()
        while (hasNext()) {
            val nextName = nextName()
            if (nextName != "eventos" && nextName != "error") {
                val value = reader.nextString()
                when (nextName) {
                    "codEnvio" -> parcel.codEnvio = value
                    "refCliente" -> parcel.refCliente = value
                    "codProducto" -> parcel.codProducto = value
                    "fecha_calculada" -> parcel.fechaCalculada = value
                    "largo" -> parcel.largo = value
                    "ancho" -> parcel.ancho = value
                    "alto" -> parcel.alto = value
                    "peso" -> parcel.peso = value
                }
                continue
            }
            if (nextName == "error") {
                val error = errorAdapter.fromJson(reader)
                if (error != null) {
                    parcel.error = error
                }
                continue
            }

            if (peek() == JsonReader.Token.BEGIN_OBJECT) {
                val fromJson = eventAdapter.fromJson(reader)
                if (fromJson != null) {
                    events += fromJson
                }
                continue
            }
            beginArray()
            while (hasNext()) {
                val fromJson = eventAdapter.fromJson(reader)
                if (fromJson != null) {
                    events += fromJson
                }
            }
            endArray()

        }
        endObject()
        return parcel
    }


    override fun toJson(writer: JsonWriter, value: CorreosApiParcel?) {
    }

    companion object {
        val FACTORY: JsonAdapter.Factory = Factory { type, _, moshi ->


            if (Types.getRawType(type) != CorreosApiParcel::class.java)
                return@Factory null

            val eventAdapter = moshi.adapter<CorreosApiEvent>(CorreosApiEvent::class.java)
            val errorAdapter = moshi.adapter<Error>(Error::class.java)
            return@Factory CorreosApiParcelAdapter(eventAdapter, errorAdapter)
        }
    }
}

据我所知,Moshi 和 Retrofit 本身不支持它,所以答案是否定的。

这是后台团队无法协调的事情吗?如果您尝试并设法在 Android 端处理它,它将变得非常复杂。我现在能看到的唯一解决方法是让后续方法调用进行验证,并使用您自己的实现将 JSON 字符串解析为有意义的数据类型。例如var events: List<Event>.

有人讨论过是否允许这种类型的响应作为 JSON 文本格式标准化的一部分。

您可以在此处阅读更多相关信息:

Does JSON syntax allow duplicate keys in an object?

Moshi 不支持多个字段,因此您必须实施自定义 JsonAdapter for it. With fromJson() you have access to the JsonReader,您可以使用它来确定 events是一个对象或数组。

override fun fromJson(reader: JsonReader) = with(reader) {
    val events = mutableList<Event>()
    beginObject()
    while (hasNext()) {
        if (nextName() != "events") { 
            skipValue()
            continue
        }
        if (peek() == BEGIN_OBJECT) {
            events += eventAdapter.fromJson(reader)
            continue
        }
        beginArray()
        while(hasNext()) {
            events += eventAdapter.fromJson(reader)
        }
        endArray()
    }
    endObject()
    Parcel(events)
}

该实现将名为 events 的所有属性读取为对象或数组,具体取决于所提供的属性。所有其他属性都被跳过,因为它们与 Parcel 无关。 eventAdapter 是您必须提供的依赖项。它负责从 json.

中读取 Event 个对象