Problem
How to parse either a single Warning
object or a list of Warning
objects (List<Warning>
) from an API using Moshi?
The response as a single warning:
{
"warnings": {...}
}
The response as a list of warnings:
{
"warnings": [{...}, {...}]
}
Trial and Error
Tried to shoehorn an autogenerated Moshi adapter. Tried to build on top of it but failed.
Solution
Generalized approach with a factory
I tried to translate the adapter Eric wrote from Java to Kotlin since I realized that a more general approach is better much like Eric points out in his reply.
Once it works, I will revise this post to make it easier to understand. A bit messy now I'm sorry.
EDIT: I ended up using the solution Eric suggested in another thread (translated into Kotlin).
Adapter with factory
package org.domain.name
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.util.Collections
import java.lang.reflect.Type
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FIELD
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val factory = SingleToArrayAdapterFactory()
}
override fun fromJson(reader: JsonReader): Any? =
if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) {
Collections.singletonList(elementAdapter.fromJson(reader))
} else delegateAdapter.fromJson(reader)
override fun toJson(writer: JsonWriter, value: Any?) =
throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects")
class SingleToArrayAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<Any>? {
val delegateAnnotations = Types.nextAnnotations(annotations, SingleToArray::class.java) ?: return null
if (Types.getRawType(type) !== List::class.java) throw IllegalArgumentException("Only List can be annotated with @SingleToArray. Found: $type")
val elementType = Types.collectionElementType(type, List::class.java)
val delegateAdapter: JsonAdapter<List<Any>> = moshi.adapter(type, delegateAnnotations)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(elementType)
return SingleToArrayAdapter(delegateAdapter, elementAdapter)
}
}
}
Qualifier
Note: I had to add the @Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
Usage
Annotate a field you want to make sure is parsed as a list with @SingleToArray
.
data class Alert(
@SingleToArray
@Json(name = "alert")
val alert: List<Warning>
)
and add the adapter factory to your Moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.factory)
.build()