Spring boot - Deserialize enum by value lookup - spring-boot

I have the following enum:
public enum BusinessType {
C_CORP("C-Corporation"),
S_CORP("S-Corporation"),
// more types
public final String name;
BusinessType(String name) {
this.name = name;
}
}
The front end will submit the name property as a string (eg. C-Corporation), how can I get spring boot to deserialize / look up the correct enum? Currenty it tries to look up the actual string on the enum, giving the error:
No enum constant ai.interval.common.model.enums.BusinessType.C-Corporation
This is the property I have in the view received from the front end:
BusinessType getBusinessType();
Thanks

Build a getter for your fields, then add the #JsonValue annotation to your getter.
public enum BusinessType {
...
#JsonValue
public String getName() {
return name;
}
}
You can find other options in section 4 of this link: How To Serialize and Deserialize Enums with Jackson

Related

Query parameters binding into DTO, how to ignore specific param?

Is there any good way to ignore some request query parameters from binding to DTO parameters?
#Data // setters and getters
class Dto {
private String param1;
private String param2;
}
#GetMapping("/hello-world")
#ResponseBody
public ResponseEntity<Dto> sayHello(Dto params)
{ ... }
I would like to exclude binding of specific param in such case.
After some research I can't find any solution for it, any help?

How to validate DTO string field is one of specified values

My spring boot application has a DTO class for creating a task.
public class CreateTaskDTO {
#NotBlank
private String status;
}
I want the type field to be one of these values: "DONE", "IN_PROGRESS", "OPEN".
Is there hibernate constraints or any other workarounds I can validate thie field?
public enum Status() {
DONE, IN_PROGRESS, OPEN
}
public class CreateTaskDTO {
#NotNull
private Status status;
}
Enum must be created. The created enum should also be used in the DTO.

Jackson java.util.Optional serialization does not include type ID

I got the following classes:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
...
}
public class MySubEntity extends Entity {
...
}
Now when I serialize that MySubEntity wrapped in an Optional then JSON does not contain the clazz attribute containing the type ID. Bug? When I serialize to List<MySubEntity> or just to MySubEntity it works fine.
Setup: jackson-databind 2.9.4, jackson-datatype-jdk8 2.9.4, serialization is done in Spring Boot application providing a RESTful web service.
EDIT: Here is the Spring REST method that returns the Optional:
#RequestMapping(method = RequestMethod.GET, value = "/{uuid}", produces = "application/json")
public Optional<MySubEntity> findByUuid(#PathVariable("uuid") String uuid) {
...
}
EDIT:
I made a SSCCE with a simple Spring REST controller and two tests. The first test is using ObjectMapper directly which is successful in deserialization although the clazz is missing. The second test calls the REST controller and fails with an error because clazz is missing:
Error while extracting response for type [class com.example.demo.MySubEntity] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'
This, indeed, looks like a bug. There is one workaround that I can suggest for this case, is to use JsonTypeInfo.As.EXISTING_PROPERTY and add field clazz to your Entity. There only one case with this approach is that the clazz must be set in java code manually. However this is easy to overcome.
Here is the full code for suggested workaround:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, //field must be present in the POJO
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
#JsonProperty
private String uuid;
//Here we have to initialize this field manually.
//Here is the simple workaround to initialize in automatically
#JsonProperty
private String clazz = this.getClass().getSimpleName();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
}

Java Jackson writeAsString conversion

using Java8 with jackson
I have code convert A class into json String, which works fine, i have Scenario where if i want values of rate1 and rate2 to be blank string if certain values.
I am wondering if there is quick and easy way to do it ?
#JsonAutoDetect(fieldVisiblity=ANY)
class A{
UUID id;
CustomEnumType type;
BigDecimal value;
BigDecimal rate1
BigDecimal rate2;
}
A a = new A();
// a filled up with values
string jsonStr = objectMapper.writeValueAsString(a);
I tried using custom Seralizer class as following but not sure how do i convert UUID string representation ?
public class ASerializer extends StdSerializer<A>{
... ASerializer()..
#Override
public void serialize(A value, JsonGenerator g, SerializerProvider p){
g.writeStratObject();
// how do i convert UUID to string ?
g.writeEndObject();
}
}
Just call toString, UUID has static fromString method to deserialize.

Spring Boot Controller endpoint and ModelAttribute deep access

I would like to know how to access a deep collection class attribute in a GET request. My endpoint maps my query strings through #ModelAttribute annotation:
Given that:
public class MyEntity
{
Set<Item> items;
Integer status;
// getters setters
}
public class Item
{
String name;
// getters setters
}
And my GET request: localhost/entities/?status=0&items[0].name=Garry
Produces bellow behavior?
#RequestMapping(path = "/entities", method = RequestMethod.GET)
public List<MyEntity> findBy(#ModelAttribute MyEntity entity) {
// entity.getItems() is empty and an error is thrown: "Property referenced in indexed property path 'items[0]' is neither an array nor a List nor a Map."
}
Should my "items" be an array, List or Map? If so, thereĀ“s alternatives to keep using as Set?
Looks like there is some problem with the Set<Item>.
If you want to use Set for the items collection you have to initialize it and add some items:
e.g. like this:
public class MyEntity {
private Integer status;
private Set<Item> items;
public MyEntity() {
this.status = 0;
this.items = new HashSet<>();
this.items.add(new Item());
this.items.add(new Item());
}
//getters setters
}
but then you will be able to set only the values of this 2 items:
This will work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa
This will not work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa&items[2].name=aaa
it will say: Invalid property 'items[2]' of bean class MyEntity.
However if you switch to List:
public class MyEntity {
private Integer status;
private List<Item> items;
}
both urls map without the need to initialize anything and for various number of items.
note that I didn't use #ModelAttribute, just set the class as paramter
#GetMapping("map")//GetMapping is just a shortcut for RequestMapping
public MyEntity map(MyEntity myEntity) {
return myEntity;
}
Offtopic
Mapping a complex object in Get request sounds like a code smell to me.
Usually Get methods are used to get/read data and the url parameters are used to specify the values that should be used to filter the data that has to be read.
if you want to insert or update some data use POST, PATCH or PUT and put the complex object that you want to insert/update in the request body as JSON(you can map that in the Spring Controller with #RequestBody).

Resources