How to serialize a class implementing a interface with gson? - kotlin

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

Related

Tell Kotlin that a generic type is an interface

Let's say I have a class Foo
class Foo {
val noProblem = "Hakuna Matata"
}
I now want to decorate it with an ID. Kotlin's delegates make this relatively painless:
class IdentifiableFoo(
val id: Int,
foo: Foo,
): Foo by foo
interface Foo {
val noProblem: String
}
class FooImpl: Foo {
override val noProblem = "Hakuna Matata"
}
but let's say I have another class Bar that I also want to decorate, and then another Baz, and then another ...
I could just create a IdentifiableXYZ for each of them, of course.
But what I really want is something akin to
class Identifiable<T> (
val id: Int,
thing: T
): T by thing
That I could just use for all of them.
And yes, there's a very good chance that the language doesn't support something like that, but the error message made me think:
Only classes and interfaces may serve as supertypes
so can I do some where magic or something to tell Kotlin that T is required to be an interface?
It is not possible in Kotlin. Kotlin only allows super type to be either a class or an interface. Type parameter cannot be a super type of some class or interface.
One workaround might be using a base interface for all the interfaces you are using. But not sure it solves your problem.
class IdentifiableFoo<T : BaseFoo>(
val id: Int,
val foo: T,
) : BaseFoo by foo {
fun doSomething(a: T.() -> String) {
println(a.invoke(foo))
}
}
interface Foo0 : BaseFoo {
val noProblem: String
}
interface Foo1 : BaseFoo {
val someProblem: String
}
class FooImpl : Foo0 {
override val noProblem = "Hakuna Matata"
}
class Foo1Impl() : Foo1 {
override val someProblem: String = "Some Problem"
}
interface BaseFoo {}
Usage:
IdentifiableFoo<Foo0>(2, FooImpl()).doSomething {
this.noProblem
} // Prints "Hakuna Matata"
IdentifiableFoo<Foo1>(2, Foo1Impl()).doSomething {
this.someProblem
} // Prints "Some Problem"
Playground link

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

Serialize enum field into JSON in Kotlin

I've got a stupid question that stunned me a bit.
I have an enum and a data class like this:
enum class MyEventType(val typeName: String) {
FIRST("firstEventReceived")
}
data class MyEvent(
val id: String,
val event: MyEventType
)
I need to send this as a json string, but common desearilizer makes such a json
{
"id": "identifier",
"event": "FIRST"
}
but i need
{
"id": "identifier",
"event": "firstEventReceived"
}
As far as i understand, kotlin allows to override getter in data class, but i didn't succeed in it... Trying to make
data class MyEvent(
val id: String
) {
val event: MyEventType get() event.typeName
}
but i've missed something, i guess...
The simplest way is probably to annotate the property with #JsonValue:
enum class MyEventType(#JsonValue val typeName: String) {
FIRST("firstEventReceived")
}
data class MyEvent(
val id: String,
val event: MyEventType
)
fun main() {
MyEvent(id = "foo", event = MyEventType.FIRST)
.let { jacksonObjectMapper().writeValueAsString(it) }
.let { println(it) }
}
Prints:
{"id":"foo","event":"firstEventReceived"}
The easiest way is to annotate the typeName with #JsonValue. This will serialise and deserialise the enum field as you want.
enum class MyEventType(#JsonValue val typeName: String) {
FIRST("firstEventReceived");
}
An alternative is to use #JsonFormat (if you are using jackson version < 2.9);
enum class MyEventType(#JsonFormat(shape = JsonFormat.Shape.OBJECT) val typeName: String) {
FIRST("firstEventReceived");
}
Herer's an example;
#JvmStatic
fun main(args: Array<String>) {
val mapper = jacksonObjectMapper()
val json = mapper.writeValueAsString(MyEvent("1", MyEventType.FIRST))
println(json)
val event = mapper.readValue<MyEvent>(json)
println(event)
}
You get the output;
{"id":"1","event":"firstEventReceived"}
MyEvent(id=1, event=FIRST)
I used Jackson version 2.12.0. Here's a good read on enum manipulation with Jackson - https://www.baeldung.com/jackson-serialize-enums
Also you can have enum with 2+ fields which you want to be serialized
enum class MyEventType(
val firstField: String,
val secondField: String,
val thirdField: String
) {
MY_ENUM("firstFieldValue", "secondFieldValue", "thirdFieldValue")
}
You can chose one of the following two options:
Put #JsonValue over a method(lets call it getter) that will return the required value(if you need only part of the fields):
#JsonValue
fun getSerializedObject(): String {
return "{firstField: $firstField, thirdField: $thirdField}"
}
Result will be "{firstField: firstFieldValue, thirdField: thirdFieldValue}"
Put #JsonFormat(shape = JsonFormat.Shape.OBJECT) over your enum class(for serialization class as common class):
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
enum class MyEventType(
val firstField: String,
val secondField: String,
val thirdField: String
) {
MY_ENUM("firstField", "secondField", "thirdField")
}
Result will be "{"firstField": "firstFieldValue", "secondField": "secondFieldValue", "thirdField": "thirdFieldValue"}"
For GSON users, you can use the #SerializedName annotation:
enum class ConnectionStatus {
#SerializedName("open")
OPEN,
#SerializedName("connecting")
CONNECTING,
#SerializedName("closed")
CLOSED
}

Access the set of abstract properties on sealed sub classes (in kotlin)

I'm got a situation where I have a common property that must be defined on each of the subclasses of a sealed class.
I'd like the ability to be able to access the set/list of these values without 'duplicating' the list (by hard coding it)
Hopefully the below code conveys what I mean
sealed class S {
companion object {
// want to avoid typing: listOf("these", "values", please")
// instead grab it from the classes themselves
val properties = S::class.sealedSubclasses.map { /* What to do here? */ }
}
abstract val property: String
}
class A(val d: String) : S() {
override val property: String = "these"
}
class B(val e: String) : S() {
override val property: String = "values"
}
class C(val f: String) : S() {
override val property: String = "please"
}
I'm aware of fun <T : Any> KClass<T>.createInstance(): T from kotlin.reflect.full, but my constructors have non optional parameters.
You can create a createInstance(vararg) extension function for that:
fun <T : Any> KClass<T>.createInstance(vararg args: Any): T =
java.constructors.first().newInstance(*args) as T
S::class.sealedSubclasses.map { it.createInstance("the string") }

How to obtain all subclasses of a given sealed class?

Recently we upgraded one of our enum class to sealed class with objects as sub-classes so we can make another tier of abstraction to simplify code. However we can no longer get all possible subclasses through Enum.values() function, which is bad because we heavily rely on that functionality. Is there a way to retrieve such information with reflection or any other tool?
PS: Adding them to a array manually is unacceptable. There are currently 45 of them, and there are plans to add more.
This is how our sealed class looks like:
sealed class State
object StateA: State()
object StateB: State()
object StateC: State()
....// 42 more
If there is an values collection, it will be in this shape:
val VALUES = setOf(StateA, StateB, StateC, StateC, StateD, StateE,
StateF, StateG, StateH, StateI, StateJ, StateK, StateL, ......
Naturally no one wants to maintain such a monster.
In Kotlin 1.3+ you can use sealedSubclasses.
In prior versions, if you nest the subclasses in your base class then you can use nestedClasses:
Base::class.nestedClasses
If you nest other classes within your base class then you'll need to add filtering. e.g.:
Base::class.nestedClasses.filter { it.isFinal && it.isSubclassOf(Base::class) }
Note that this gives you the subclasses and not the instances of those subclasses (unlike Enum.values()).
With your particular example, if all of your nested classes in State are your object states then you can use the following to get all of the instances (like Enum.values()):
State::class.nestedClasses.map { it.objectInstance as State }
And if you want to get really fancy you can even extend Enum<E: Enum<E>> and create your own class hierarchy from it to your concrete objects using reflection. e.g.:
sealed class State(name: String, ordinal: Int) : Enum<State>(name, ordinal) {
companion object {
#JvmStatic private val map = State::class.nestedClasses
.filter { klass -> klass.isSubclassOf(State::class) }
.map { klass -> klass.objectInstance }
.filterIsInstance<State>()
.associateBy { value -> value.name }
#JvmStatic fun valueOf(value: String) = requireNotNull(map[value]) {
"No enum constant ${State::class.java.name}.$value"
}
#JvmStatic fun values() = map.values.toTypedArray()
}
abstract class VanillaState(name: String, ordinal: Int) : State(name, ordinal)
abstract class ChocolateState(name: String, ordinal: Int) : State(name, ordinal)
object StateA : VanillaState("StateA", 0)
object StateB : VanillaState("StateB", 1)
object StateC : ChocolateState("StateC", 2)
}
This makes it so that you can call the following just like with any other Enum:
State.valueOf("StateB")
State.values()
enumValueOf<State>("StateC")
enumValues<State>()
UPDATE
Extending Enum directly is no longer supported in Kotlin. See
Disallow to explicitly extend Enum class : KT-7773.
With Kotlin 1.3+ you can use reflection to list all sealed sub-classes without having to use nested classes: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/sealed-subclasses.html
I asked for some feature to achieve the same without reflection: https://discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087
Full example:
sealed class State{
companion object {
fun find(state: State) =
State::class.sealedSubclasses
.map { it.objectInstance as State}
.firstOrNull { it == state }
.let {
when (it) {
null -> UNKNOWN
else -> it
}
}
}
object StateA: State()
object StateB: State()
object StateC: State()
object UNKNOWN: State()
}
A wise choice is using ServiceLoader in kotlin. and then write some providers to get a common class, enum, object or data class instance. for example:
val provides = ServiceLoader.load(YourSealedClassProvider.class).iterator();
val subInstances = providers.flatMap{it.get()};
fun YourSealedClassProvider.get():List<SealedClass>{/*todo*/};
the hierarchy as below:
Provider SealedClass
^ ^
| |
-------------- --------------
| | | |
EnumProvider ObjectProvider ObjectClass EnumClass
| |-------------------^ ^
| <uses> |
|-------------------------------------------|
<uses>
Another option, is more complicated, but it can meet your needs since sealed classes in the same package. let me tell you how to archive in this way:
get the URL of your sealed class, e.g: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
scan all jar entry/directory files in parent of sealed class URL, e.g: jar://**/com/xxx/app or file://**/com/xxx/app, and then find out all the "com/xxx/app/*.class" files/entries.
load filtered classes by using ClassLoader.loadClass(eachClassName)
check the loaded class whether is a subclass of your sealed class
decide how to get the subclass instance, e.g: Enum.values(), object.INSTANCE.
return all of instances of the founded sealed classes
If you want use it at child class try this.
open class BaseSealedClass(val value: String, val name: Int) {
companion object {
inline fun<reified T:BaseSealedClass> valueOf(value: String): T? {
return T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
.associateBy { it.value }[value]
}
inline fun<reified T:BaseSealedClass> values():List<T> =
T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
}
}
#Stable
sealed class Theme(value: String, name: Int): BaseSealedClass(value, name) {
object Auto: Theme(value = "auto", name = R.string.setting_general_theme_auto)
object Light: Theme(value= "light", name = R.string.setting_general_theme_light)
object Dark: Theme(value= "dark", name = R.string.setting_general_theme_dark)
companion object {
fun valueOf(value: String): Theme? = BaseSealedClass.valueOf(value)
fun values():List<Theme> = BaseSealedClass.values()
}
}
For a solution without reflection this is a library that supports generating a list of types to sealed classes at compile time:
https://github.com/livefront/sealed-enum
The example in the docs
sealed class Alpha {
object Beta : Alpha()
object Gamma : Alpha()
#GenSealedEnum
companion object
}
will generate the following object:
object AlphaSealedEnum : SealedEnum<Alpha> {
override val values: List<Alpha> = listOf(
Alpha.Beta,
Alpha.Gamma
)
override fun ordinalOf(obj: Alpha): Int = when (obj) {
Alpha.Beta -> 0
Alpha.Gamma -> 1
}
override fun nameOf(obj: AlphaSealedEnum): String = when (obj) {
Alpha.Beta -> "Alpha_Beta"
Alpha.Gamma -> "Alpha_Gamma"
}
override fun valueOf(name: String): AlphaSealedEnum = when (name) {
"Alpha_Beta" -> Alpha.Beta
"Alpha_Gamma" -> Alpha.Gamma
else -> throw IllegalArgumentException("""No sealed enum constant $name""")
}
}
The short version is
State::class.sealedSubclasses.mapNotNull { it.objectInstance }