I'm trying to encode a sealed class
json.encodeToString(
Filter.NOT(
Filter.Field(
name = "foo", value = "bar"
)
)
)
and output something completely different like
{
"not": {
"foo": "bar"
}
}
to exactly match the expected JSON of an API I'm integrating into.
The data structure is a sealed interface (with and, or, xor, etc, but removed for simplicity):
@Serializable
sealed interface Filter {
...
@Serializable
@SerialName("not")
class NOT(val not: Filter): Filter
@Serializable(with = Field.Companion::class)
data class Field(
val name: String,
val value: String
) : Filter {
companion object : KSerializer<Field> {
override val descriptor: SerialDescriptor
get() = mapSerialDescriptor<String, String>()
override fun serialize(encoder: Encoder, value: Field) {
println(value)
encoder.encodeStructure(mapSerialDescriptor<String, String>()) {
mutableMapOf("a" to "b", value.name to value.value)
}
}
override fun deserialize(decoder: Decoder): Field = TODO()
}
}
If I encodeToString, I currently get:
{
"not": {
"type": "kotlin.collections.HashMap"
}
}
if I remove the custom serializer
{
"not": {
"type": "com.jvaas.testing.model.filter.Filter.Field",
"name": "foo",
"value": "bar"
}
}
So the type should not be there, and name + value should be encoded "name": "value" instead of "name": name, "value": value to achieve
{
"not": {
"foo": "bar"
}
}
Trying with
@Serializable(with = Field.Companion::class)
data class Field(
val name: String,
val value: String
) : Filter {
companion object : JsonTransformingSerializer<Field>(Field.serializer()) {
override fun transformSerialize(element: JsonElement): JsonElement {
return JsonObject(mutableMapOf<String, JsonElement>().apply {
put("foo", JsonPrimitive("bar"))
})
}
}
}
I'm getting
Caused by: java.lang.NullPointerException: Cannot invoke "com.jvaas.testing.model.filter.Filter$Field$Companion.serializer()" because "com.jvaas.testing.model.filter.Filter$Field.Companion" is null
Any idea how this can be done?
EDIT: Example JSON added as requested
{
"filter": {
"or": [
{
"and": [
{
"field1_id": "1"
},
{
"field2_id": "A"
}
]
},
{
"nand": [
{
"field3_id": "2"
},
{
"field4_id": "B"
}
]
},
{
"xor": [
{
"field5_id": "3"
},
{
"field6_id": "C"
}
]
},
{
"nor": [
{
"field7_id": "D"
},
{
"not": {
"field8": [
"EF",
"GHI",
"JK"
],
"operator": "in"
}
}
]
}
]
}
}
Modelling the and / or / nor / nand / xor / xnor / not part is the easy part, that just works.
@Serializable
sealed interface Filter {
@Serializable
@SerialName("and")
class AND(val and: List<Filter>) : Filter {
constructor(vararg and: Filter) : this(and.toList())
}
@Serializable
@SerialName("or")
class OR(val or: List<Filter>) : Filter {
constructor(vararg or: Filter) : this(or.toList())
}
...
@Serializable(with = FieldSerializer::class)
data class Field(
val name: String,
val value: String
) : Filter {
...
}
}
also quite intuitive to work with too
json.encodeToString(
Filter.NOT(
Filter.AND(
Filter.Field(
name = "foo", value = "bar"
),
Filter.OR(
Filter.Field(
name = "stack", value = "overflow"
),
Filter.Field(
name = "rab", value = "oof"
),
)
)
)
)
The field part however needs to be able to take any dynamic name (let's say for argument sake field[a-zA-Z0-9_]*) plus a value and output
{
'field_that_can_be_anything_unknown_at_compile_time': 'some value'
}
You'll notice in the example JSON that a field can either have a single value or a list of values. For this StackOverflow question, only consider the single value field as part of the question, I can easily create another field type called FieldIn to handle the list of values and solve it with the same solution that is used for a Field with a single value.
ororandto get a better understanding of the overall structure you're aiming to achieve?