Using enum as #PathVariable in FeignClient - spring

I don't know how to make FeignClient working with enum serialization as #PathVariable.
I have enum type representing Access Type:
public enum StorageFileAccessType {
TOKEN(0),
SMSCODE(1);
private final int id;
private StorageFileAccessType(int id) {
this.id = id;
}
#JsonValue
public int getId() {
return id;
}
...
}
Each enum value has corresponding int value, which should be used during serialization.
Then I have FeignClient with one method for uploading file: it uploads file for given user and sets access type (token or smscode).
#FeignClient(value = "uploadClient", url = "${serverUrl}", configuration = { ClientConfig.class })
public interface UploadClient {
#RequestMapping(method = RequestMethod.POST, value = "files/write/{user}/{accessType}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Result uploadFile(#PathVariable("user") String user,
#PathVariable("accessType") StorageFileAccessType accessType,
#RequestPart("file") MultipartFile file);
}
Now when I call method, e.g:
feignClient.uploadFile("user", StorageFileAccessType.TOKEN, file);
I expect request is sent with URI: ${serverUrl}/files/write/user/0
but instead it is sent with URI: ${serverUrl}/files/write/user/TOKEN what means Enum serialization to integer is not performed and enum name is used.
When I use enum inside #RequestPart, serialization to int works correctly.
Is there any way to make this Enum serialization working also for #PathVariable?
Of course I can change method parameter to:
#PathVariable("accessType") int accessType
and pass int directly - and it works, but If possible I prefer using enum - as more cleaner solution.

Related

Use converter to get form data parameters into a map inside a dto

I have a controller method in spring boot:
#PostMapping(produces = "text/html")
public String create(#Valid myDTO myDTO, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
// Omissions
return "redirect:/blue/tar/";
}
I have a dto:
public class MyDTO {
private Long id;
private List<FooBar> objects;
public MyDTO(Long id, List<FooBar> objects) {
this.id = id;
this.objects = objects;
}
public Long getId() {
return id;
}
public List<FooBar> getObjects() {
return objects;
}
}
And I have a converter, which i have added to the converterRegistry in spring:
private Converter<String, FooBar> getStringToLegalEntityConverter() {
return new org.springframework.core.convert.converter.Converter<>() {
public FooBar convert(#NotNull String id) {
return //* convert id to FooBar structure*//
}
};
}
My web form send form data. E.g:
fooBar: 1
fooBar: 3
fooBar: 4
And the above code handles this perfectly. The converter is called three times with 1, 3 and 4. The result a bit magically appears as a list with three FooBar objects in the dto in the controller.
The above works.
I attempted to use a map in MyDTO instead. Spring complains that there is no converter from String -> Map.
If i add one (which feels wrong, since there was no converter from String -> List before), it only enters the converter once with the last value. (4, in the example above).
So is there a solution here to get spring to allow me to manually convert a series of form params to a map in such a way that they end up in a DTO like that?
Below is my attempt which fails. Since spring only attempts to convert one value to fit in the map in the DTO. I would have needed the converter to be called with all the form parameters at once:
private Converter<String, Map<Long, FooBar>> attempt() {
return new org.springframework.core.convert.converter.Converter<>() {
public Map<Long, FooBar> convert(#NotNull String aString) {
/* Say if aString is the formparameters as "1,3,4" then i'd turn those into keys and then fetch their values from a db */
return /* The map */
}
};
}
Similar but I don't think the answers there are applicable: How to get Form data as a Map in Spring MVC controller?

How Spring HttpMessageConverter parse request body to the model that I define

I am a bit curious how spring handle this, and I did some experiment.
Here is my request handling method:
#PostMapping(value = "/testRequestBody")
public String testRequestBody(#RequestBody MyRequestBody requestBody) {
System.out.println(requestBody);
return "Success";
}
I have tried three types of MyRequestBody.
Tyep 1:
public class MyRequestBody {
private int id;
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Type 2:
public class MyRequestBody {
private int id;
private String name;
public MyRequestBody(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Type 3:
public class MyRequestBody {
private int id;
private String name;
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Both Type 1 and Type 2 I can get MyRequestBody(id=1,name=test name) in the console, for input {"id": 1,"name": "test name"}, and Type 3 gives me MyRequestBody(id=0,name=null). It seems Spring is able to choose different way to parse my model based on how setters and constructors are defined for my model. I would like to know how does Spring achieve that?
MappingJackson2HttpMessageConverter delegates to Jackson, the work of converting json to a java object. So, the behaviour that you are seeing can be explained by the rules jackson follows.
Following is a simplified sequence of the operations that Jackson performs, which explains the behavior that you are seeing with the three MyRequestBody definitions.
An object of the class is created by invoking the constructor. If the constructor accepts parameters, the values for the parameters are taken from the json string. Following are the rules to decide which constructor should be invoked.
If there is a constructor annotated with #JsonCreator, that constructor is invoked.
If there is no constructor annotated with #JsonCreator
If there is exactly one constructor, that constructor is invoked. This could be either a parameterized constructor or a default constructor.
If there are multiple constructors, including a default constructor, the default constructor is invoked.
In other cases, an exception is thrown.
Field values are set by calling setX() methods.
Java reflection is used for examining the constructor/methods and invoking them.
So, in your case
For the first RequestBody definition, default constructor is invoked and setX() methods are called.
For the second RequestBody definition, parameterized constructor is invoked (there is no default constructor)
The default constructor is invoked.

Spring Boot - How can I pass custom values in HTTP Post api?

I'm new with Spring Boot and I have difficult to understand how can I pass data. For example:
I want pass those data to my server:
{
"code", 1,
"name": "C01"
}
So I have create always a custom Object with code and name as attributes to have this http post api?
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
Another solution I see that can be this but I can't pass numbers (int code), right?
#RequestMapping(value = "/new/{code}/{name}", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
Kind regards :)
You can pass code and name as PathVariables just like in your example:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
A PathVariable can be an int or a String or a long or a Date, according to the docs:
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.
You could also define a PathVariable of type Map<String, Object> like this:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("map") Map<String, Object> map) {
Integer code = (Integer) map.get("code");
String name = (String) map.get("name");
...
}
You could even use #RequestParam and supply the data in the form of URL query parameters.
So, there are numerous ways in which data can be passed to a Spring MVC controller (more details in the docs) but I think the convention for posting complex data (by "complex" I mean more than a single piece of state) is to define a request body which contains a serialised form of that complex state i.e. what you showed in the first example in your queston:
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
If this question is about RESTful best practice, since you are developing webservice for creating an Order object, this is how I would design it
Order.java
public class Order {
private Integer code;
private String name;
public Integer getCode() {
return code;
}
public void setCode(final Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
Controller
#RequestMapping(value = "/orders", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Order> createOrder(#Valid #RequestBody Order order){
...
}
Technically, you can do many things to achieve the same thing, but that will not be a RESTful service, it will be an RPC at best.

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;
}
}

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