It is a common pattern for APIs to return single object wrapped in an array as in following JSON:
[
{
"foo": "bar",
"lorem": "ipsum",
"num": 42
}
]
which should be deserialized to:
data class Example(
val foo: String,
val lorem: String,
val num: Int)
Jackson already implements this feature, it can be enabled with property UNWRAP_SINGLE_VALUE_ARRAYS. More info: https://github.com/FasterXML/jackson-databind/issues/381.
It would be nice to have similar option here.
I would advocate against baking any support for this in. You should be able to accomplish this yourself by writing a custom serializer and using @Serializable(with=UnwrapSingleValueArray::class).
@slomkowski I faced the same issue and I quickly developed a "workaround" but that's not really the best way to implement it... Anyway, hope it will help to unblock some devs:
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.Serializer
import kotlinx.serialization.encode
import kotlinx.serialization.internal.ArrayListSerializer
import kotlinx.serialization.internal.SerialClassDescImpl
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElementSerializer
@Serializer(forClass = List::class)
class UnwrapSingleValueArray<T : Any>(private val dataSerializer: KSerializer<T>) : KSerializer<List<T>> {
override val descriptor: SerialDescriptor = object : SerialClassDescImpl("UnwrapSingleValueArray") {}
override fun deserialize(decoder: Decoder): List<T> {
return when (val jsonElt = JsonElementSerializer.deserialize(decoder)) {
is JsonArray -> jsonElt.content.map { jsonClient.parse(dataSerializer, it.toString()) }
else -> listOf(jsonClient.parse(dataSerializer, jsonElt.toString()))
}
}
override fun serialize(encoder: Encoder, obj: List<T>) {
encoder.encode(ArrayListSerializer(dataSerializer), obj)
}
}
Note: the jsonClient is just the kotlinx.serialization.json.Json object.
@slomkowski
To me it's laughable nonsense, there shouldn't be a custom serializer for this simple issue, some people I know just end up replacing first [ and last ] with something more "parsable" from this library.
This is what they are doing:
arrayJsonString.replaceFirst(Regex("\\["), "{\"container\":[").reversed().replaceFirst("]", "}]").reversed()
@Serializable
data class ContainerObj(val container:List<Example>)
data class Example(
val foo: String,
val lorem: String,
val num: Int)
Otherwise rather use gson and be done.
how cheap is .reverse on megabytes of inner-json?
Most helpful comment
@slomkowski
To me it's laughable nonsense, there shouldn't be a custom serializer for this simple issue, some people I know just end up replacing first [ and last ] with something more "parsable" from this library.
This is what they are doing:
arrayJsonString.replaceFirst(Regex("\\["), "{\"container\":[").reversed().replaceFirst("]", "}]").reversed()Otherwise rather use gson and be done.