I got a simple data class for Kotlin
data class Person(val name: String, #get: Min(18) val age: Int)
I actually build this class from a CSV file and I read the CSV using apache CSV parser. Even I have some data which is less than 18 for age field, the test still passed no error.
Looks like this annotation is not working for Kotlin?
You're currently annotating the constructor parameter with the Min annotation - I believe you should annotate the field instead, with a use-site target like this:
data class Person(val name: String, #field:Min(18) val age: Int)
Try adding the annotation#Validated to the class (might just work for spring beans though). These annotations do not prevent the value being set they simply allow getting a BindingResult with a list of validation errors from an existing instance. If you need to prevent a value being set you can use a custom delegate.
val age: Int by MyDelegates.min(18)
Related
I'm trying to serialize and deserialize an enum with Jackson.
My enum:
public enum class Type {
#JsonProperty("Typ A")
TypeA,
#JsonProperty("Typ B")
TypeB,
}
Serializing Type.TypeA results in the desired outcome of "Typ A". However Deserializing "Typ A" results in the following error:
java.lang.IllegalArgumentException: No enum constant de.advisori.pzp.task.TaskType.Typ A
I have tried other variations that I found online, such as this:
public enum class Type (#JsonValue val value: String) {
TypeA("Typ A"),
TypeB("Typ B"),
}
but they all yield the same result. Serialization works, deserialization results in the error above.
How do I correctly deserialize an enum with Jackson?
If it makes any difference: I am using it in a Spring Boot RequestMapping as a #RequestParam and return value.
As #dnault pointed out, Jackson isn't used for deserialization here. #RequestParams are never treated as JSON, hence Jackson is never used on them.
Two possible solutions are:
Using Kotlins ability to use spaces in names:
public enum class Type { `Typ A`, `Typ B` } (suggested by #DodgyCodeException)
Using a explicitly defined converter: https://stackoverflow.com/a/69031139/12898394 (pointed in the right direction by #Michal Ziober
I don't think any annotations will work to change the enum values. For this you need to write your own Serializer and Deserializer.
You will likely want to do this:
Create a Serializer by subclassing StdSerializer
Create a Deserializer by subclassing StdDeserializer
If you intend on using the enum as a key in JSON you will need KeyDeserializer too
Create a Module to wrap these up that you can pass to the configuration of Jackson, for that you use SimpleModule
There are many tutorials for this, e.g. https://www.baeldung.com/jackson-deserialization
I am trying to set up my application's Spring Cloud config objects using a Kotlin data class. I've been successful with using #ConstructorBinding when defining classes that exist once at a specific node in the config but have run into a roadblock with objects that only appear in a child list.
Sample config:
toolbox:
tools:
- id: 1
description: hammer
- id: 2
description: screwdriver
What I would like to be able to do is this:
#ConstructorBinding
#ConfigurationProperties("toolbox")
data class ToolBox(val tools: List<Tool>) {
#ConstructorBinding
data class Tool (val id: String, val description: String)
}
But when I do this I receive errors like this:
Property: toolbox.tools[0].description
Value: hammer
Origin: "toolbox.tools[0].description" from property source "bootstrapProperties-https:/some-config.yml"
Reason: The elements [toolbox.tools[0].description,toolbox.tools[0].id,toolbox.tools[1].description,toolbox.tools[1].id] were left unbound.
My application is configured with #ConfigurationPropertiesScan and other uses of #ConstructorBinding work for me. For example, the following alternative configuration works:
#ConstructorBinding
#ConfigurationProperties("toolbox")
data class ToolBox(val tools: List<Tool>) {
class Tool {
lateinit var id: String
lateinit var description: String
}
}
I've tried adding #ConfigurationProperties to the Tool class but that annotation requires a prefix and I don't know of a way to define a prefix that addresses into a list like I am trying to use here.
Is there a way to use #ConstructorBinding with a configuration class that only appears within a list?
I came across a problematic to which I can't find any nice solution. Some context: we work with several micro-services, most of which use rest clients. We found out that a lot of them will use similar configurations for similar issues (i.e. resiliency). Naturally, we want to extract common, heavily duplicated, non business code into a library. But here is the thing: How can I extract a #ConstructorBinding #ConfigurationProperties data class in a library (especially if there could be several instances of these classes in the code base that uses the library)?
Here is some example code:
#ConstructorBinding
#ConfigurationProperties(prefix = "rest.client")
data class MyDuplicatedRestClientProperties(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
)
I would like to import this in a project to configure 2 different REST clients. I tried:
Creating an abstract class my ClientProperties would extend. Sadly, I need to expose all the fields of the parent class which doesn't really help with duplication:
abstract class MyAbstractClient(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
)
#ConstructorBinding
#ConfigurationProperties(prefix = "rest.client")
class MyImplematationClient(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
): MyAbstractClient(
host,
someOtherField,
someFieldWithDefaultValue
)
Instantiating the properties as a #Bean method with the #ConfigurationProperties but this doesn't work well either as it forces me to put fields with #Value in the #Configuration class:
#Configuration
class MyConfigurationClass {
#Value("${my.client.host}")
lateinit var host: String
#Value("${my.client.someOtherField}")
lateinit var someOtherField: Int
#Value("${my.client.someFieldWithDefaultValue:default value}")
lateinit var someFieldWithDefaultValue: String
#Bean
#ConfigurationProperties
fun myClient() = MyDuplicatedRestClientProperties(
host,
someOtherField,
someFieldWithDefaultValue
)
}
From my experience, you're on a wrong road. Why?
Duplication in microservices is allowed. Code is not too large, it's decoupled and can be easily changed.
From Distributed Systems theory, sharing classes between multiple components it's a bad thing. Why? Because doing this will couple the components via those classes.
A better approach will be to encapsulate all the integration into a specific library such as a REST client. For example, accessing Service A can be done via a service-a-client.jar which will contain the configuration and the integration that is necessary in order to call the Service A and will expose one or multiple interfaces that can be used as Spring Beans.
Putting the configuration into a library gives you no advantage, configurations are not business related, they are somehow synthetic objects and have no value in the architecture.
I have a RestController with one endpoint. That endpoint accepts object of data class. Data class has 2 attributes. How do I make sure these attributes are validated?
My data class:
data class FormObject(val email: String, val age: Int)
And controller:
#PostMapping("submit")
fun submit(#RequestBody formObject: FormObject): FormObject {
return formObject
}
How do I make sure email is email and age is not bigger than 150?
Thanks,
You can use the Bean Validation Framework for this.
1) Annotate the request object as needing validation:
fun submit(#Valid #RequestBody formObject: FormObject): FormObject
^^^^^^
2) Annotate the fields of your data class with the appropriate validation annotations:
data class FormObject(
#field:NotBlank
val email: String,
#field:Min(1)
#field:Max(150)
val age: Int
)
Note that you have to apply the annotation to the field (not the parameter) or the validation won't happen the way we want. Also, if we define age as an Int, it will have a default value (0) if the caller does not send it, so I applied a the minimum validation on that to offset that (assuming age 0 is not ok, YMMV).
I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.