I am having the following problem in Kotlin when deserializing a LocationGeneric with Jackson. It is a case when I add no extra info to the abstract class I use to construct the concrete classes. It works good when I deserialize LocationOne or LocationTwo.
This is the code I have written:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", visible = true)
#JsonSubTypes(
JsonSubTypes.Type(value = LocationOne::class, name = "ONE"),
JsonSubTypes.Type(value = LocationTwo::class, name = "TWO"),
JsonSubTypes.Type(value = LocationGeneric::class, name = "GENERIC_1"),
JsonSubTypes.Type(value = LocationGeneric::class, name = "GENERIC_2")
)
abstract class Location(
val type: String
)
class LocationGeneric(
type: String
): Location(type)
class LocationOne(
type: String,
val somethingSpecific: String
): Location(type)
class LocationAirport(
type: String,
val somethingElse: String
): Location(type)
This is the error I am getting:
Cannot construct instance of Location (although at least one Creator
exists): cannot deserialize from Object value (no delegate- or
property-based Creator)
I tried changing the abstract class to an open class, but had no luck so far. I works for the other cases. Why doesn't it find the default Kotlin constructor in the LocationGeneric case? Any ideas?
The problem I had was that I somehow Jackson lost visibility of the constructor, so I provided a default implementation for the generic cases and annotated the constructor and attributes of my generic implementation.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", visible = true, defaultImpl = LocationGeneric::class)
#JsonSubTypes(
JsonSubTypes.Type(value = LocationOne::class, name = "ONE"),
JsonSubTypes.Type(value = LocationTwo::class, name = "TWO")
)
abstract class Location(
val type: String
)
class LocationGeneric #JsonCreator constructor(
#JsonProperty("type") type: String
): Location(type)
This also allows me to add an init to handle weird mapping cases.
Related
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" }
]}
*/
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...
My use case:
I have a large number of POJO models that are different types of requests for a third-party API. All of them have several common fields and a couple unique ones.
I was hoping to build something that conceptually looks like this
class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
commonField1 = "1",
commonField2 = "2",
...
uniqueFieldA = "A"
)
}
I can of course override the common fields in every child request and then pass them to the parent constructor, but this ends up producing a lot of boilerplate code and bloats the model. Are there any options I can explore here?
Notice that what you are doing in the parentheses that follow a class declaration is not "declaring what properties this class has", but "declaring the parameters of this class' primary constructor". The former is just something you can do "along the way", by adding var or val.
Each class can have its own primary constructor that take any number and types of parameters that it likes, regardless of what class its superclass is. Therefore, it is not unreasonable to have to specify all the parameters of the constructor:
open class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
// notice that the parameters for the inherited properties don't have the
// "val" prefix, because you are not declaring them in the subclass again.
// These are just constructor parameters.
commonField1: String,
commonField2: String,
...
commonFieldX: String,
val uniqueFieldA: String,
): RequestBase(
commonField1,
commonField2,
...
commonFieldX,
)
If you find this unpleasant, there are a bunch of ways to work around this.
One way is to use composition and delegation - create an interface having the common properties. The specific requests' primary constructors will take a RequestBase and their unique properties, and implement the interface by delegating to the RequestBase:
interface Request {
val commonField1: String
val commonField2: String
val commonFieldX: String
}
open class RequestBase(
override val commonField1: String,
override val commonField2: String,
override val commonFieldX: String
): Request
class RequestA(
val requestBase: RequestBase,
val uniqueField: String
): Request by requestBase
This allows you to access someRequestA.commonFieldX directly, without doing someRequestA.requestBase.commonFieldX, but to create a RequestA, you need to create a RequestBase first:
RequestA(
RequestBase(...),
uniqueField = ...
)
Another way is to change your properties to vars, give them default values, and move them out of the constructor parameters:
open class RequestBase {
var commonField1: String = ""
var commonField2: String = ""
var commonFieldX: String = ""
}
class RequestA: RequestBase() {
var uniqueField: String = ""
}
Then to create an instance of RequestA, you would just call its parameterless constructor, and do an apply { ... } block:
RequestA().apply {
commonField1 = "foo"
commonField2 = "bar"
commonFieldX = "baz"
uniqueField = "boo"
}
The downside of this is of course that the properties are all mutable, and you have to think of a default value for every property. You might have to change some properties to nullable because of this, which might not be desirable.
You can't do it with constructors of base class. Without constructors it's possible:
open class RequestBase {
lateinit var commonField1: String
lateinit var commonField2: String
...
lateinit var commonFieldX: String
}
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
uniqueFieldA = "A"
).apply {
commonField1 = "1"
commonField2 = "2"
...
commonFieldX = "X"
}
}
Good morning. I'm trying to figure out how to deserialize a parameter but I can't find the solution. In practice, the response JSON should populate or not a field based on the value I get from another type of field, which is numeric and can be 0 or 1.
In particular (seeing following serialization) in MyData class there is a property called "mutable_value" that can be 1 or 0. when is 1 I must serialize b_property, else I must serialzie c_property.
I'm trying to achieve it but I don't know where I can do my case if 0 or 1...
The class I serialize is the following:
data class ClassResponseData(
#JsonProperty("code")
val code: String,
#JsonProperty("data")
val data: MyData,
) {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
visible = true,
property = , // <-------------what I must do here?
defaultImpl = MyData.DefaultValue::class
)
#JsonSubTypes(
JsonSubTypes.Type(
value = MyData.BType::class,
name = "new_domain"
),
JsonSubTypes.Type(
value = MyData.CType::class,
name = "new_domain"
)
)
sealed class MyData{
data class DefaultValue(
#JsonProperty("b_value")
val bValue: String,
#JsonProperty("c_value")
val cValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
)
data class BType(
#JsonProperty("b_value")
val bValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
) : MyData()
data class CType(
#JsonProperty("c_value")
val cValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
) : MyData()
}
}
What I'm doing wrong?
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