How to annotate type argument in Kotlin? - spring-boot

Essentially, I'm trying to get something like this
public class Post {
public final #Nullable Optional<#Size(min = 10) String> title;
public Post(#JsonProperty("title") Optional<String> title) {
this.title = title;
}
}
but in Kotlin.
I've tried
class Post(
#field:Size(min = 10) val title: Optional<String>?
)
but that annotates the title property and not the String inside the Optional.
Is there a way to achieve this in Kotlin?
The context of this is to use validators in Spring Boot to validate the given string, if it is present. The Optional is there to allow me to distinguish between the user sending
{
"title": null
}
or
{}

Related

How to change spring resources list name

I have a controller returning all cars in my database. It is achieved by putting the car list into Resources(see the code). I want to be able to rename the list's name from 'carDTOList' to 'carList". How to do that?
public class CarDTO {
private String id;
private UserDTO owner;
private String brand;
private String model;
private String color;
private String plate;
private String additionals;
#GetMapping("/cars")
public ResponseEntity<?> getAllCars() {
List<Resource<CarDTO>> cars = StreamSupport.stream(repository.findAll().spliterator(), false)
.map(car -> assembler.toResource(modelMapper.map(car, CarDTO.class)))
.collect(Collectors.toList());
Resources<Resource<CarDTO>> carsResource = new Resources<Resource<CarDTO>>(cars, ControllerLinkBuilder
.linkTo(ControllerLinkBuilder.methodOn(CarController.class).getAllCars()).withSelfRel());
return ResponseEntity.ok(carsResource);
}
{
"_embedded": {
"carDTOList": [
{
"id": "5d5bc8144a8fb83fd42120e1",
"owner": {
"id": "5d5bc8144a8fb83fd42120de",
As you see in the response it is set to 'carDTOList'
You can use Spring annotation:
#org.springframework.hateoas.core.Relation(value = "resource", collectionRelation = "resources")
to annotate your DTO class. So now when you return one element it will be called resource. If you return list it will be called resources.
If you want to keep the CarDTO Java name, then take advantage of the Jackson annotation to change naming:
#JsonRootName("car")
public class CarDTO {
Based on the config you have, when a collection is returned then a List suffix will be added.. resulting in carList.
you can refactor (rename the file) CarDTO class to CarList. That should do it.

How do we do kotlin data class validation on Collection?

I am trying to add validation to my data models in kotlin, The simple fields are easy to do using the #field annotations. But, I am struggling to do the same with collections.
I have uploaded the issue to github here
The java model is working with no issues but the kotlin version is not. I am adding both the models here.
public class JavaUser {
#NotEmpty
#NotNull
#Pattern(regexp = "[a-z]*", message = "Only lower case first name")
private String name;
private List<
#NotNull
#NotEmpty
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
String> phones;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getPhones() {
return phones;
}
public void setPhones(List<String> phones) {
this.phones = phones;
}
}
data class KotlinUser(
#field:NotEmpty
#field:NotNull
#field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
val name: String,
// Cannot use #field here, anything else we could use?
val phones: List<
#NotNull
#NotEmpty
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
String>
)
My tests - The java test passes but the kotlin one fails
#Test
fun `java user validation`() {
val javaUser = JavaUser()
javaUser.name = "sadfjsjdfhsjdf"
javaUser.phones = listOf("dfhgd")
webTestClient.put().uri("/user/java")
.body(BodyInserters.fromObject(javaUser))
.exchange()
.expectStatus().is4xxClientError
}
#Test
fun `kotlin user validation`() {
val kotlinUser = KotlinUser(name = "sadfjsjdfhsjdf", phones = listOf("dfhgd"))
webTestClient.put().uri("/user/kotlin")
.body(BodyInserters.fromObject(kotlinUser))
.exchange()
.expectStatus().is4xxClientError
}
Controller
#RestController
class Controller {
#PutMapping("/user/java")
fun putUser(#RequestBody #Valid javaUser: JavaUser): Mono<ResponseEntity<String>> =
Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))
#PutMapping("/user/kotlin")
fun putUser(#RequestBody #Valid kotlinUser: KotlinUser): Mono<ResponseEntity<String>> =
Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))
}
Any help would be greatly appreciated. Thank you!
This is currently not supported. The Kotlin compiler currently ignores annotations on types.
See for details:
https://discuss.kotlinlang.org/t/i-have-a-question-about-applying-bean-validation-2-0/5394
Kotlin data class and bean validation with container element constraints
There are also issues on the Kotlin issue tracker for this:
https://youtrack.jetbrains.net/issue/KT-26605
https://youtrack.jetbrains.net/issue/KT-13228
The latter has target version 1.4.
Try this:
data class KotlinUser(
#field:NotEmpty
#field:NotNull
#field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
val name: String,
#field:Valid
#field:NotEmpty
val phones: List<Phone>
)
data class Phone(
#NotBlank
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
val phoneNumber: String?
)
If you don't stick to Bean Validation, YAVI can be an alternative.
You can define the validation as follows:
import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.builder.forEach
import am.ik.yavi.builder.konstraint
data class KotlinUser(val name: String,
val phones: List<String>) {
companion object {
val validator = ValidatorBuilder.of<KotlinUser>()
.konstraint(KotlinUser::name) {
notEmpty()
.notNull()
.pattern("[a-z]*").message("Only lower case first name")
}
.forEach(KotlinUser::phones, {
constraint(String::toString, "value") {
it.notNull().notEmpty().pattern("\\d{10}").message("Only 10 digits")
}
})
.build()
}
}
Here is a YAVI version of your code.
A workaround there is to use the #Validated from spring in your controller.
Example:
#RestController
#RequestMapping("/path")
#Validated
class ExampleController {
#PostMapping
fun registerReturns(#RequestBody #Valid request: List<#Valid RequestClass>) {
println(request)
}
}
RequestClass:
#JsonIgnoreProperties(ignoreUnknown = true)
data class RequestClass(
#field:NotBlank
val field1: String?,
#field:NotBlank
#field:NotNull
val field2: String?)
This way Beans Validation is applied.

Spring Boot request body semi-required fields

In our application user can write a message based on user id or screen name.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = ???) Long userId,
#JsonProperty(value = "screen_name", required = ???) String screenName,
#JsonProperty(value = "text", required = true) String text) {
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}
Fields userId and screenName can't be optional at same time, one should be provided.
How in Spring Boot to mark that they are semi-required?
This seems like more of a validation concern rather than deserialization.
Create a Validator then put #Valid within the #RequestMapping on the controller.
See more here:
Spring REST Validation Example
From jenkov tutorials:
#JsonValue
The Jackson annotation #JsonValue tells Jackson that Jackson should
not attempt to serialize the object itself, but rather call a method
on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by
the custom serialization, so you cannot return e.g. a full JSON
object. For that you should use #JsonRawValue instead (see previous
section).
The #JsonValue annotation is added to the method that Jackson is to
call to serialize the object into a JSON string. Here is an example
showing how to use the #JsonValue annotation:
public class PersonValue {
public long personId = 0;
public String name = null;
#JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
The output you would get from asking Jackson to serialize a
PersonValue object is this:
"0,null"
So you can use #JsonValue and put your code either to ignore or not from some fields when you try to convert into JSON
#JsonValue
public String toJson(){
//ignore fields or include them here
}
Just throw an IllegalArgumentException. The best case would be to deserialize, then run through a validator though so you separate the concerns of serialization, and domain validation.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = false) Long userId,
#JsonProperty(value = "screen_name", required = false) String screenName,
#JsonProperty(value = "text", required = true) String text) {
if(userId == null && screenName == null) {
throw new IllegalArgumentException("userId or screenName must be provided.");
}
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Change property names while deserialzing class to JSON in Spring MVC

I'm trying to consume a rest API call using Spring as below:
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
HttpEntity<String> request = new HttpEntity<String>(headers);
RestTemplate restTemplate = new RestTemplate();
Item item = restTemplate.exchange(url, HttpMethod.GET, request, Item.class).getBody();
The response I get from the API is in following form:
{
"item":[{
"itemname": "abc",
"qty":...
}]
}
The Item class has following fields:
Class Item{
#JsonProperty("itemname")
String name;
#JsonProperty("qty")
int quantity;
// Getter / setter methods
}
I've added JsonProperty annotations to the fields as their names are different from the json I get from the API. With this, I'm able to deserialize the api response successfully.
However, when I try to serialize the Item class again as a json, the field names are "itemname" and "qty". Is there any way to keep these as "name" and "quantity", and yet be able to map to the API response?
Thanks in advance.
If you just want to serialize in different form, you can do it like this:
public static class Item {
private String name;
private int quantity;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("itemname")
public void setName(String name) {
this.name = name;
}
#JsonProperty("quantity")
public int getQuantity() {
return quantity;
}
#JsonProperty("qty")
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
This would read "{"itemname": "abc", "qty":10 }" and write "{"name": "abc", "quantity":10 }".
But there is a big drawback - you wont be able to read "{"name": "abc", "quantity":10 }", with this ObjectMapper (This is worse possible solution).
You can use 2 ObjectMappers and instead of class Annotations use Mixins, to configure specific deserialization
This is how your Mixin would look like:
abstract public static class ItemMixin {
ItemMixin(#JsonProperty("itemname") String itemname, #JsonProperty("qty") int qty) { }
// note: could alternatively annotate fields "w" and "h" as well -- if so, would need to #JsonIgnore getters
#JsonProperty("itemname") abstract String getName(); // rename property
#JsonProperty("qty") abstract int getQuantity(); // rename property
}
Here is how to add Mixin in ObjectMapper.
objectMapper.addMixIn(Item.class, ItemMixinA.class);
So if you Deserialize with Mixin ObjectMapper, and serialize with standard ObjectMapper there will be no problem.
You can write custom JsonDeserialization for your class.
It's easy to do for class with few fields, but complexity would grow proportionally as number of fields grows.
Try using #JsonAlias annotation to specify the other property names that can be used for deserializing the object. More information can be gotten from here:
https://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonAlias.html

Resources