I've got a json response that looks like this for some cases:
{
"id" : 12345,
"events": [
{
"desc": "Bla bla"
...
},
{
"desc": "Yada yada",
...
},
]
}
Whilst for some other scenarios it looks like this:
{
"id" : 12345,
"events": {
"desc": "Bla bla"
...
},
"events" : {
"desc": "Yada yada"
...
},
}
That is, sometimes events
will be an array, sometimes events
is duplicated with multiple values. This throws the following exception using 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$2.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)
I'd like to standardize the output for both scenarios, that is, I would like to turn this into
data class Parcel(val events: List<Event>)
I know the second kind of response is malformed but I have no control over the backend (its an external service that im consuming), is there a way to fix this?
I tried fiddling with custom adapters but i cant make heads or tails of how to do it :(
EDIT: My best attempt at a Custom Adapter:
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)
}
}
}