Kotlin json serialization, list with any value - kotlin

I have the following data class which I'm trying to serialize.
#Serializable
data class QueryResponseDto(
val results: List<Result>
) {
#Serializable(with = Result.PacketSerializer::class)
data class Result(
val columns: List<String>,
val types: List<String>,
val values: List<List<#Contextual Any>>
)
}
This is the form of my json.
{"results":[{"columns":["id","name"],"types":["int","varchar"],"values":[[1,"Store one"]]}]}
Is this achievable using the serialization? I have no idea of how to make the serializer builder for the 2D Any array.

Related

Serialization with sealed classes fails in Kotlin serialization

I'm having trouble with kotlin-serialization in the following use case:
#Serializable
sealed class NetworkAnswer {
#SerialName("answerId")
abstract val id: Int
}
#Serializable
data class NetworkYesNoAnswer(
override val id: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
When I serialize this:
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
val result: NetworkYesNoAnswer = json.decodeFromString(NetworkYesNoAnswer.serializer(), """
{
"answerId": 1,
"isPositive": true
}
""".trimIndent()
)
I get the following error
Caused by: kotlinx.serialization.MissingFieldException: Fields [id] are required for type with serial name 'NetworkYesNoAnswer', but they were missing
The only way the serialization works is if I use the same name for both the member and "SerialName", like so:
#Serializable
sealed class NetworkAnswer {
#SerialName("answerId")
abstract val answerId: Int
}
#Serializable
data class NetworkYesNoAnswer(
override val answerId: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
This kinda defeats the purpose of "SerialName", is there a way to solve that without using the same name?
Declaring a #SerialName on a base class has no effect on member declarations overridden by child classes.
Instead, you can declare #SerialName on the child class instead. There is no need to change the actual name of the field.
#Serializable
data class NetworkYesNoAnswer(
#SerialName("answerId")
override val id: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
Declaring the #SerialName on the base class and applying it to all children seems NOT to be supported as of now, but is desired by other members of the community as well, e.g. here on GitHub.
OT: Most likely you could use a sealed interface, which was first introduced in Kotlin v1.5.0, instead of a sealed class.

How to use custom field in polymorphic Json deserialization using kotlinx.serialization

I have the following data structure that I want to deserialize:
#Serializable
data class SearchResponse(val results: List<SearchResultContainer>) {
#Serializable
data class SearchResultContainer(
val type: ResultType,
val result: SearchResult
)
#Serializable
enum class ResultType {
SERIES, SERIES_CRUMB, EPISODE, CHANNEL
}
#Serializable
sealed interface SearchResult
#Serializable
data class SeriesSearchResult(
val id: String,
val name: String,
val description: String,
val image: String
) : SearchResult
// ...
I want to deserialize concrete SearchResult based on enum - ResultType.
Do I need to register custom serializer for this?

can I use kotlinx serializer with multiple sealed class levels as parents and a nested invocation?

I am trying to use kotlinx #Serializable and Ive faced this issue:
I have the following classes:
#Serializable
sealed class GrandParent
a second one:
#Serializable
sealed class Parent() : GrandParent() {
abstract val id: String
}
and a third one
#Serializable
data class Child(
override val id: String, ....
): Parent()
I'm needing of grandparent since I use it as a generic type in another class, which happen to also have a reference to the GrandParent class
#Serializable
data class MyContent(
override val id: String,
....
val data: GrandParent, <- so it has a self reference to hold nested levels
...): Parent()
Every time I try to run this I get an error...
Class 'MyContent' is not registered for polymorphic serialization in the scope of 'GrandParent'.
Mark the base class as 'sealed' or register the serializer explicitly.
I am using ktor as wrapper, kotlin 1.5.10. I did this based on https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#registered-subclasses
Any ideas?
You should serialize and deserialize using your sealed class in order for kotlin serialization to "know" to add a discriminator with the right implementation. By default it search for type in the json but you can change it with JsonBuilder:
Json {
classDiscriminator = "class"
}
Here is an example:
#Serializable
sealed class GrandParent
#Serializable
sealed class Parent : GrandParent() {
abstract val id: String,
}
#Serializable
data class Child(
override val id: String,
): Parent()
#Serializable
data class MyContent(
override val id: String,
val data: GrandParent,
): Parent()
fun main() {
val test = MyContent(id = "test", data = Child(id = "child"))
val jsonStr = Json.encodeToString(GrandParent.serializer(), test)
println("Json string: $jsonStr")
val decoded = Json.decodeFromString(GrandParent.serializer(), jsonStr)
println("Decoded object: $decoded")
}
Result in console:
Json string: {"type":"MyContent","id":"test","data":{"type":"Child","id":"child"}}
Decoded object: MyContent(id=test, data=Child(id=child))
encode and decode can also be written like this (but behind the scenes it will use reflections):
val jsonStr = Json.encodeToString<GrandParent>(test)
println("Json string: $jsonStr")
val decoded = Json.decodeFromString<GrandParent>(jsonStr)
println("Decoded object: $decoded")

Transform data class to map kotlin

My problem is that I need to transform a data class in kotlin to a map, because I need to work with this structure as a requirement, because this response will be used for a groovy classes and there is a post-process where there are validations iterations etc, with this map. My data class is the next (Podcast):
data class PodCast(val id: String, val type: String, val items: List<Item>, val header: Header, val cellType:String? = "")
data class Item(val type: String, val parentId: String, val parentType: String, val id: String, val action: Action, val isNew: Boolean)
data class Header(val color: String, val label: String)
data class Action(val type: String, val url: String)
I made the transformation manually, but I need a more sophisticated way to achieve this task.
Thanks.
You can also do this with Gson, by serializing the data class to json, and then deserializing the json to a map. Conversion in both directions shown here:
val gson = Gson()
//convert a data class to a map
fun <T> T.serializeToMap(): Map<String, Any> {
return convert()
}
//convert a map to a data class
inline fun <reified T> Map<String, Any>.toDataClass(): T {
return convert()
}
//convert an object of type I to type O
inline fun <I, reified O> I.convert(): O {
val json = gson.toJson(this)
return gson.fromJson(json, object : TypeToken<O>() {}.type)
}
See similar question here
I have done this very simple. I got the properties of the object, just using the .properties groovy method, which gave me the object as a map.

Creating a map with values containing generics

I want to create a map with a key being a single object and a value being many objects, some containing generics. Is there a concise way to do this in Kotlin? I've used data classes in the past, but haven't found a way to make that work with generics.
Thanks!
Edit: Here's an example:
class SomeClass<E> {
data class Data(val str: String, val int: Int, val e: E) //the last value is invalid
val map: MutableMap<String, Data> = mutableMapOf()
}
Working from your example, this should work for you.
data class Data<E>(val str: String, val int: Int, val e: E)
class SomeClass<E> {
val map: MutableMap<String, Data<E>> = mutableMapOf()
}
I'm defining Data as an external, generic class, and use that inside the actual class.
Edit: Actually, you don't even need to move the data class outside of the outer class:
class SomeClass<E> {
data class Data<T>(val str: String, val int: Int, val e: T)
val map: MutableMap<String, Data<E>> = mutableMapOf()
}