I want to deserialise the following json in a typed object using a generic for the parameter `value`...
j
I want to deserialise the following json in a typed object using a generic for the parameter
value
. It can be a string, double, boolean or a list of string.
Copy code
[
  {
	"name": "travel_money_active",
	"value": true,
	"description": "Travel money is set to active"
  }, ...
]
I have created this types for it
Copy code
@Serializable
abstract class Setting<T> {
    abstract val name: String
    abstract val value: T
    abstract val description: String?
}

@Serializable
class BooleanSetting(
    override val name: String,
    override val value: Boolean,
    override val description: String? = null,
): Setting<Boolean>()

...

val settingSerializerModule = SerializersModule {
    polymorphic(baseClass = Setting::class, baseSerializer = null) {
        subclass(BooleanSetting::class)
        subclass(DoubleSetting::class)
        subclass(StringSetting::class)
        subclass(StringListSetting::class)
    }
}

...

install(ContentNegotiation) {
        json(
            Json {
                serializersModule = settingSerializerModule
                prettyPrint = true
                isLenient = true
            },
        )
    }
In one test, I use the following
Copy code
val settings = Json.decodeFromString<List<Setting<*>>>(response)
assertTrue(settings.size == 7)
but I get
Star projections in type arguments are not allowed, but had Setting<*>
What can I do to make it work?
a
generics and Kotlinx Serialization can work, but it can be quite complicated. Is the generic type completely open to be any type, or is it going to be closed? For example, will
value
always be a primitive?
j
For now it’s always a primitive yes
I guess I realize my mistake : I should remove the generic type and use a sealed class or something like that?
Yes that works, thanks for hinting my on the correct path!
a
okay cool, then there are a few options: 1. remove the generic type, and replace
val value: T
with
val value: JsonPrimitive
. Pro: it’s quick and simple, and the decoding becomes a case of some extra Kotlin code (which is generally easier to write and debug than custom KxS serializers) Con: It’s slightly less type-strict, so it would allow for decoding ‘invalid’ types 2. convert Setting to a sealed interface, and create specific subtypes for each primitive type of value. Create a custom JsonContentPolymorphicSerializer to select the correct subclass. Pro: much more explicit and strict Con: Verbose and repetitive, and is not easy to scale if there are ever multiple generic args 3. create a custom
sealed interface SettingValue
type and replace
val value: T
with
val value: SettingValue
. Create subtypes as value classes for each allowed subtype. Create a custom JsonContentPolymorphicSerializer to select the correct value type.
Copy code
@Serializable(with =  SettingValueSerializer::class)
sealed interface SettingValue
value class SettingValueString(val value: String): SettingValue, CharSequence by value
value class SettingValueDouble(val value: Double): SettingValue
...
This is basically a manual version of JsonPrimitive. Pro: More explicit, easier to scale/adapt, can re-use the value classes in multiple hierarchies (for example, if one JSON value can be a string/boolean, then create a new sealed interface and make SettingValueString & SettingValueBoolean extend from it Con: It can be annoying to work with value classes in Kotlin (even if you do some interface delegation), so you might end up with as much Kotlin conversion code as if you used a JsonPrimitive.
j
I tried JsonPrimitive at first but the fact it only has
isString
was making it difficult to use, I’ll check the other options. Thanks for the help!
a
you’re very welcome!
there are lots of options for getting the content of a JsonPrimitive (e.g. doubleOrNull, booleanOrNull…), but they’re extension functions so maybe your IDE missed them? https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/
j
I didn’t see there was an extension list, I’ll check that too