Kotlin sealed class - kotlin

I have below class, I would like to make this class a sealed class. Can you please help me as I am new to Kotlin.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = A:class, name = "PIZZA"),
JsonSubTypes.Type(value = B::class, name = "DONUT"),
JsonSubTypes.Type(value = C::class, name = "ICECREAM"),
JsonSubTypes.Type(value = D::class, name = "CHOCOLATE"),
)
open class food (var type: foodType, var quantity : String) {
open val taste : String=""
}
How to make this a sealed class perhaps a subclass of a sealed class, and how to instantiate it?
The foodType is enum class
enum class foodType {
PIZZA,
DONUT,
ICECREAM,
CHOCOLATE
}
I have the following based on the other post, but I am confused on passing the right parameters. Can someone help me understand what parameter I need to pass??
sealed class food (var type: foodType, var quantity: String) {
class favFood(taste: String): food(?, ?)
}

What is a sealed class ?
When you create a sealed class, you only allow the implementations you
created, just like for an enum (Only the constants you added are allowed). Once the module is compiled, you can't add any additional implementation anymore (in opposite to an open class).
Here is the link to the Kotlin documentation about sealed classes : https://kotlinlang.org/docs/sealed-classes.html
Sealed classes are interesting when you want to restrict the implementations
to a strict proposition. It can be the case with your use case, to restrict the jsonSubTypes you allow (others wouldn't be mapped).
How to transform an open class to a sealed class ?
So to transform your open class to a sealed class, you generally just need to change the keyword open to sealed. However, you also need to understand how the inheritance mechanism work with sealed classes.
For your example
With JsonSubType, you just need to map the property type to an implementation of your sealed class using a constant of your choice.
Also, you have to provide the values to your sealed class' properties when you extend it, so when you create your implementations.
In the next example, you can find how to give a value to your sealed class properties and what will be the result when you map it to json using JSonSubType :
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = Pizza::class, name = "Pizza"),
JsonSubTypes.Type(value = Donut::class, name = "DonutDesert"), // As you can see, name is a value you give, not always need to be the class name
JsonSubTypes.Type(value = IceCream::class, name = "IceCream")
)
sealed class Food(val taste: String)
class Pizza(val size: PizzaSize, taste: String) : Food(taste) {
enum class PizzaSize {
SMALL,
MEDIUM,
LARGE
}
}
class Donut(val glaze: String, taste: String) : Food(taste)
class IceCream(val servings: Int, taste: String) : Food(taste)
class Basket(foods: List<Food>)
/* If you map a Basket to JSON, it will give you this :
{ foods: [
{ "type": "Pizza", "size": "MEDIUM", "taste": "Hawaii" },
{ "type": "DonutDesert", "glaze": "Sugar & Marshmallows", "taste" : "chocolate"},
{ "type": "IceCream", "servings": 3, "taste": "Strawberry" }
]}
*/

Related

Mixin adding defaultImpl in Jackson does not work

I want to use Jackson mixin to provide a default implementation for an abstract type:
#JsonTypeInfo(
use = Id.NAME,
include = As.PROPERTY,
property = "type",
visible = true,
defaultImpl = GenericRequest::class
)
#JsonMixin(Request::class)
class AlexaRequestMixin {
}
data class GenericRequest(
val type: String, val requestId: String, val timestamp: OffsetDateTime
)
Base class that I want to alter with a mixin:
#JsonTypeInfo(
use = Id.NAME,
include = As.PROPERTY,
property = "type",
visible = true
)
#JsonSubTypes({#Type(
value = InstallationError.class,
name = "Alexa.DataStore.PackageManager.InstallationError"
),
// ...
)})
public abstract class Request {
My objectMapper:
However when I try to deserialize a class that is not present as subtype I get:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'Foo' as a subtype of 'com.amazon.ask.model.Request': known type ids = [...]
In order to make it work I had to:
Make GenericRequest extend from the abstract Request class.
When an abstract class is from Java and inheriting data class is from Kotlin it causes lots of problems.
a) Data class cannot override same properties from abstract class https://youtrack.jetbrains.com/issue/KT-6653/Kotlin-properties-do-not-override-Java-style-getters-and-setters
b) I had to change include = As.PROPERTY to include = JsonTypeInfo.As.WRAPPER_ARRAY
So I ended up with implementing GenericRequest extending Request in Java...

Jackson Serializaiton/Deserialization by custom property in enum

I want to make Jackson work with enums not by name and not by ordinal, but with a custom property I added called "stringId".
I wanted to support this with all Enums in the system so I made an interface called StringIdEnum which the FooEnum will implement.
I'm using Kotlin so I created a property in the interface called stringId which I override in each enum value.
Now I want to make Jackson serialize and deserialize using this stringId field, from what I seen I have several options:
Use #JsonProperty annotation on each enum value and make sure it is aligned with the stringId property.
I see two issues with this approach. one it's a lot of annotation to add (we have many enum classes across the system). two I need to make sure the annotation value and the property value should be always the same which can cause issues in the future.
I tried to use the READ_ENUMS_USING_TO_STRING feature, but because I'm using an interface I can't override the toString in the interface class (I can override it in every enum class but that again seems like a lot of redundant code)
Implement a custom serializer/deserializer.
The serializer is pretty straightforward, however, I had trouble with the deserializer.
I wanted to register the deserializer on the StringIdEnum interface, but I had an issue getting all the runtime enum values for the actual FooType enum.
StringIdEnum:
interface StringIdEnum {
val stringId: String
}
enum class FooType(override val stringId: String) : StringIdEnum {
FOO("FOO"),
GOO("GOO");
}
Managed to get it working:
#JsonSerialize(using = StringIdEnumSerializer::class)
#JsonDeserialize(using = StringIdEnumDeserializer::class)
interface StringIdEnum: DbEnum {
val stringId: String
}
class StringIdEnumSerializer: StdSerializer<StringIdEnum>(StringIdEnum::class.java) {
override fun serialize(value: StringIdEnum, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.stringId)
}
}
class StringIdEnumDeserializer : JsonDeserializer<Enum<*>>(), ContextualDeserializer {
private lateinit var type: JavaType
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Enum<*> {
val t = p.text
val enumConstants = (type.rawClass as Class<Enum<*>>).enumConstants
return enumConstants.single { (it as StringIdEnum).stringId == t }
}
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
val wrapperType: JavaType = property!!.type
val stringIdEnumDeserializer = StringIdEnumDeserializer()
stringIdEnumDeserializer.type = wrapperType
return stringIdEnumDeserializer
}
}

Having an abstract class that defines a primary constructor variable with a default value, how can we set that immutable value from a subclass

Given an abstract class:
abstract class SuperClass(val someVal: String = "defaultValue") : SomeSuperClass
I want the implementing subclass to have two constructors, one that wouldn't set someVal, thus relying on the "defaultValue", another one that sets a different someVal, that is to say, val sc = SubClass() and val sc2 = SubClass("anotherVal")
I have tryied with:
class SubClass() : SuperClass() {
constructor(someVal: String) : this(someVal)
But the compiler complains:
Error:(5, 37) Kotlin: There's a cycle in the delegation calls chain
How can I implement it keeping someVal immutable? A solution like the following is not valid since it would be mutable:
abstract class SuperClass(var someVal: String = "defaultValue") : SomeSuperClass
class SubClass() : SuperClass() {
constructor(someVal: String) : this() {
super.someVal = someVal
}
}
If you omit the primary constructor you can do this:
class SubClass : SuperClass {
constructor(someVal: String): super(someVal)
constructor(): super()
}

Jackson : #JsonTypeId is not getting serialized properly when nested

I have a Zoo class which can contain animal of different types (Dog, ...) and has an animalType annotated with #JsonTypeId. The Dog class in turn can contain leash of different types (RopeLeash, ...) and has leashType annotated with #JsonTypeId. When i serialize the Zoo class using below method then the leashType gets set for animalType as well:
String zooJson = objectMapper.writeValueAsString(zoo);
Output is:
{"animal":{"leash":{"leashColor":"RED"},"leashType":"ROPE"},"animalType":"ROPE"}
Classes:
public class Zoo {
#JsonTypeId
private AnimalType animalType;
private Animal animal;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "animalType"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = Cat.class, name = "CAT"),
#JsonSubTypes.Type(value = Dog.class, name = "DOG")
})
public void setAnimal(Animal animal) {
this.animal = animal;
}
//Other getters and setters
}
public class Dog extends Animal {
#JsonTypeId
private LeashType leashType;
private Leash leash;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "leashType"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = RopeLeash.class, name = "ROPE")
})
public void setLeash(Leash leash) {
this.leash = leash;
}
//Other getters and setters
}
public class RopeLeash extends Leash {
private String leashColor;
//Getter and setter for leashColor
}
Is there something wrong in my annotation usage?
It seems that Jackson does not support multi-level IDs. Posted this question in the Jackson site and here is the response:
Multi-level type ids are not supported; a single id is required. No
support for multiple levels are planned to be used.
Link: https://github.com/FasterXML/jackson-databind/issues/1462

Hiding Jackson type info on certain (fields) situations?

The example
Java:
#JsonTypeInfo(
use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#type")
public class Pet{
String name;
}
public class Dog extends Pet{}
public class Cat extends Pet{}
public class PetHouse {
List<Pet> pets;
}
public class BarkingData {
int decibels;
Dog dog;
}
JSON Serialization
petHouse = {
pets :
[
{'#type': 'Dog', 'name':'Droopy'},
{'#type': 'Cat', 'name':'Scratchy'},
{'#type': 'Dog', 'name':'Snoopy'}
]
}
barkingData = {
decibels:15,
dog:{'#type':'Dog', 'name':'Droopy'}
}
The Question
Class BarkingData has a field of type Dog (cats don't bark do they). Is it possible to tell Jackson not to include typeInfo for instances where that type can be "hinted" from the declaring field ?
So that the output of Barking data looks like :
barkingData = {
decibels:15,
dog:{'name':'Droopy'}
}
Your idea that you know the dynamic type (actual type) of this field because the static type is Dog and not Animal only works if there are no subclasses of Dog. If you make the Dog class final, then Jackson knows it can safely leave out the type info.
Additionally, you can override Jackson's type info settings, in more complex ways, for fields of static type Dog, by adding a #JsonTypeInfo annotation to the definition of the Dog class.