Kotlinx.serialization: Unwrapping single value JSON arrays to objects

Created on 29 Mar 2019  路  4Comments  路  Source: Kotlin/kotlinx.serialization

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.

feature question

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()

@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.

All 4 comments

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?

Was this page helpful?
0 / 5 - 0 ratings