Parse to different type based on property - kotlin

I am new to Kotlin, and I'm trying to figure out how parsing unknown JSON works.
I have the following set of classes:
#Serializable
abstract class Node {
val name: String = ""
val type: String = ""
abstract val spec: Any
}
#Serializable
class TestNode : Node() {
override val spec: TestNodeSpec = TestNodeSpec()
}
#Serializable
class TestNodeSpec {
val test: String = "testSpec"
}
I can successfully parse an object directly to a TestNode, but for my purpose I want to be able to read the type to determine the correct class to parse it as. How is this done in Kotlin?

Related

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?

Kotlin serialization: nested polymorphic module

Recently I've been trying to implement Kotlinx Serialization for polymorphic class hierarchy. I used this guide, however my example was a bit more complex. I had the following class structure:
#Serializable
open class Project(val name: String)
#Serializable
#SerialName("owned")
open class OwnedProject(val owner: String) : Project("kotlin")
#Serializable
#SerialName("owned_owned")
class OwnedOwnedProject(val ownerOwner: String) : OwnedProject("kotlinx.coroutines")
And was trying to deserialize OwnedOwnedProject instance using following code:
val module = SerializersModule {
polymorphic(Project::class) {
polymorphic(OwnedProject::class) {
subclass(OwnedOwnedProject::class)
}
}
}
val format = Json { serializersModule = module }
fun main() {
val str = "{\"type\":\"owned_owned\",\"name\":\"kotlin\",\"owner\":\"kotlinx.coroutines\",\"ownerOwner\":\"kotlinx.coroutines.launch\"}"
val obj = format.decodeFromString(PolymorphicSerializer(Project::class), str)
println(obj)
}
However whatever combinations of SerializersModule definition I tried, it always ended up with kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'owned_owned' error.
Could you please give me a hint: how to implement SerializersModule for given class structure (deeper than two)? Am I missing something?
It seems you would need to register OwnedOwnedProject directly under Project as well, so the serializer knows it's a possible subclass:
val module = SerializersModule {
polymorphic(Project::class) {
subclass(OwnedOwnedProject::class)
}
polymorphic(OwnedProject::class) {
subclass(OwnedOwnedProject::class)
}
}

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

How to serialize a class implementing a interface with gson?

There is a interface A:
interface A {
val name: String
}
Also there is one class B implementing this interface:
class B() : A {
val implementedName: String = "Test"
override val name: String
get() = implementedName
}
Then i try to serialize this class B:
val b: A = B()
Gson().toJson(b)
And geht the following output:
// Output
{"implementedName":"Test"}
I realize the gson to type erasure can't infere the type of the variable b, but what I want to see is gson serializing the interface fields:
// Output
{"name":"Test"}
How can i achieve this?
Gson can't do that. Using SerializedName annotation Android Studio highlights that it is not possible to do this. This annotation is not applicable to target 'member property without backing field or delegate. But implementing it this way should work.
interface A {
val name: String
}
class B() : A {
#SerializedName("your_name")
override val name: String = "Test"
}
Then using Gson().toJson(b) the output should be:
{"your_name": "Test"}

Spring Data with ReactiveMongoRepository: generic saving

I would like to save some documents with the value containing a generic. And I constantly receive a StackOferflowError.
Here is a fragment of my model class
data class MyDocument {
val errors: List<SomeError> = emptyList()
}
SomeError is in interface that should be implemented by different types of errors including ValidationError
interface SomeError
data class ValidationError<T: Any>(val objectType: KClass<T>, val objectId: String) : SomeError
I am trying to save my object with non-empty list of errors (using ReactiveMongoRespoitory):
myDocumentRepository.save(MyDocument(
errors = listOf(
ValidationError<MyDocument>(objectType = MyDocument::class, objectId = "somedoc")) //as SomeError did not help
))
Do you know how I can correct it?
This is a psrt of the Stack trace:
java.lang.StackOverflowError
at java.base/java.util.HashMap.putVal(HashMap.java:624)
at java.base/java.util.HashMap.putMapEntries(HashMap.java:510)
at java.base/java.util.HashMap.putAll(HashMap.java:780)
at org.springframework.data.util.TypeDiscoverer.resolveType(TypeDiscoverer.java:168)
at org.springframework.data.util.ParameterizedTypeInformation.calculateTypeVariables(ParameterizedTypeInformation.java:269)
You can do something like this:
import org.springframework.data.annotation.Transient
data class ValidationError private constructor(
private val type: String,
val objectId: String
) : SomeError {
constructor(type: KClass<*>, objectId: String) : this(type.qualifiedName!!, objectId)
#delegate:Transient
val objectType by lazy {
this.javaClass.classLoader.loadClass(type).kotlin
}
}
Store KClass internally as fully qualified name.