I am building an app which can fetch a Warning
object from an API which I do not control nor do they want to remove this behaivor to which I am trying to work around.
I would like to be able to parse either a single Warning
object or a List<Warning>
objects. (Because the API returns either 1 object if there is only 1 or > 1 a list of objects.)
How can I parse an object as a list directly or parse list this with Moshi?
The data I get looks either like this:
{
# List<Warning>
"warnings": [{...}, {...}]
}
or like this:
{
# Warning
"warnings": {...}
}
I have read and found some sources on this topic, but no examples and I am not sure how to proceed.. so any pointers would be highly appreciated.
I initially tried to shoehorn an autogenerated moshi adapter thinking I might be able to build ontop of it but I couldn't figure out what was going on so it didn't really lead me anywhere. But I included the generated code anyway incase it might be useful for the task still.
Edit: This is what I currently have, though im not very pleased by the fact it takes an instance of Moshi to work.
I tried to port adapter which Eric wrote to kotlin since I since has realised that a more general approach is better much like Eric points out in his reply. As soon a this is working, I will re-write parts of this post to make it more understandable, its a bit messy now I apologize for that.
EDIT: I ended up using the solution Eric suggested in another thread but ported to Kotlin.
class SingleToArrayAdapter(
val delegateAdapter: JsonAdapter<List<Any>>,
val elementAdapter: JsonAdapter<Any>
) : JsonAdapter<Any>() {
companion object {
val INSTANCE = 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)
if (delegateAnnotations == null) return null
if (Types.getRawType(type) != List::class.java) throw IllegalArgumentException("Only lists may 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)
}
}
}
Note: I had to add the Target(FIELD)
.
@Retention(RUNTIME)
@Target(FIELD)
@JsonQualifier
annotation class SingleToArray
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 dont forget to add the adapter to your moshi instance:
val moshi = Moshi.Builder()
.add(SingleToArrayAdapter.INSTANCE)
.build()
the API returns either 1 object if there is only 1 or > 1 a list of objects.
Create an adapter that peeks to see if you're getting an array first.
Here is exactly what you want. It includes a qualifier, so you can apply it only to lists that may have this behavior for single items. @SingleToArray List<Warning>
.
There is another example of dealing with multiple formats here for further reading.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With