Change property names while deserialzing class to JSON in Spring MVC - spring

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

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?

Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called

I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):
EndPoints example:
#GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
try {
List<Event> events = (List<Event>) eventsRepository.findAll();
return new ResponseEntity<List<Event>>(
events, HttpStatus.OK);
}catch(IllegalArgumentException e) {
return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(#PathVariable String code){
Optional<Event> event = eventsRepository.findByCode(code);
return event.isPresent() ?
new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}
Serializer (class that extends of StdSerializer):
#Override
public void serialize(Event value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
if(firstRepresentation) {
//First Representation
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeObjectField("creation", value.getCreation());
gen.writeObjectFieldStart("event_tracks");
for (EventTrack eventTrack : value.getEventsTracks()) {
gen.writeNumberField("id", eventTrack.getId());
gen.writeObjectField("startTime", eventTrack.getStartTime());
gen.writeObjectField("endTime", eventTrack.getEndTime());
gen.writeNumberField("priority", eventTrack.getPriority());
gen.writeObjectFieldStart("user");
gen.writeNumberField("id", eventTrack.getUser().getId());
gen.writeEndObject();
gen.writeObjectFieldStart("state");
gen.writeNumberField("id", eventTrack.getState().getId());
gen.writeStringField("name", eventTrack.getState().getName());
gen.writeEndObject();
}
gen.writeEndObject();
gen.writeEndObject();
}else if(secondRepresentation) {
//Second Representation
}
}
Entity:
#JsonSerialize(using = EventSerializer.class)
#RequiredArgsConstructor
#Getter
#Setter
public class Event implements Comparable<Event>{
private Long id;
#JsonIgnore
private String code;
private Timestamp creation;
#NonNull
private String description;
#JsonUnwrapped
#NonNull
private EventSource eventSource;
#NonNull
private String title;
#NonNull
private Category category;
#NonNull
#JsonProperty("event_tracks")
private List<EventTrack> eventsTracks;
#JsonProperty("protocol_tracks")
private List<ProtocolTrack> protocolTracks;
public void addEventTrack(#NonNull EventTrack eventTracks) {
eventsTracks.add(eventTracks);
}
#JsonIgnore
public EventTrack getLastEventTrack() {
return eventsTracks.get(eventsTracks.size() - 1);
}
#JsonIgnore
public int getLastPriority() {
return getLastEventTrack().getPriority();
}
public void generateUUIDCode() {
this.code = UUID.randomUUID().toString();
}
#Override
public int compareTo(Event o) {
return this.getLastPriority() - o.getLastPriority();
}
}
So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.
Thank you.
If you want to define multiple representations of the same bean you could use Jackson JsonView.
With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.
Documentation here : https://www.baeldung.com/jackson-json-view-annotation
Just don't forget that you doing REST here....avoid expose too many representations of the same resource

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.

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.

How to send Java collections containing subclasses to spring controller

I'm trying to send collections to my spring MVC controller:
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#ModelAttribute("mapperList") MapperList mapperList) {
//prints {"users":null}
System.out.println(new ObjectMapper().writeValueAsString(mapperList));
return new ResponseDTO();
}
this is the code posting my users :
public ResponseDTO postUsers(ArrayList<User> users) {
ResponseDTO serverResponse = null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestMethod("POST");
// prints {"users":[{"property1":"x","property1":y}]}
System.out.println(objectMapper.writeValueAsString(new MapperList(users)));
objectMapper.writeValue(connection.getOutputStream(), objectMapper.writeValueAsString(new MapperList(users)));
//blabla ...
}
and this is the object containing my list :
public class MapperList implements Serializable {
private static final long serialVersionUID = 8561295813487706798L;
private ArrayList<User> users;
public MapperList() {}
public MapperList(ArrayList<User> users) {
this.setUsers(users);
}
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
}
and this is the users type to post:
public abstract class User implements Serializable {
private static final long serialVersionUID = -1811485256250922102L;
private String property1;
private String property2;
public User() {}
public User(String prop1, String prop2) {
// set properties
}
// getters and setters
}
the problem is, when I output the value of the users's array before to post it to the controller, I got the following json value :
{"users":[{"property1":"x","property1":y}]}
but in the controller, when I print what I get from the request body, I only get :
{"users":null}
I also tryed with the annotation #RequestBody instead of #ModelAttribute("mapperList") and a JSONException is displayed :
*A JSONObject text must begin with '{' at 1 [character 2 line 1]\r\n*
My array list of users contains only one user that should be displayed. I don't understand why this doesn't work...
Thanks for any help !
You can chnage your MapperList class definition as public class MapperList extends ArrayList<User>{ ..} you dont need to define any instance variable like private ArrayList users inside MapperList class. Use #Requestbody annotation. You will be able to use MapperList as a ArrayList
Try to use:
public class MapperList{
private List<User> users;
//setter and getter
//toString
}
public class User{
private String property1;
private String property2;
//getter + setter
}
json:
{"users":[{"property1":"x", "property2":"y"}]}
in controller use #RequestBody. In that case Jackson will map your json to ArrayList of users.
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList users) {
System.out.println(users);
return null;
}
no need to get objectMapper in that case. Don't forget to set content-type in request header to application/json. It required by Spring to handle #RequestBody processing.
If not working try to change MapperList:
List<User> users = new ArrayList<User>();
On the server side keep the #RequestBody annotation:
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList mapperList)
...
But this line causes problems:
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
First it converts the object to JSON and then again uses objectMapper to JSON-encode the string into output stream. Try the following instead:
connection.getOutputStream().write(
objectMapper.writeValueAsString(new MapperList(users))
.getBytes("UTF-8")
);
or directly output to stream:
objectMapper.writeValue(
connection.getOutputStream(),
new MapperList(users))
);
Zbynek gave me part of the answer. Indeed
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
doesn't work properly in my case
But moreover, my User class was an abstract class, with many type of User as subclasses. so the #RequestBody annotation couldn't work without specified the object type in the Json.
I used the following annotations on User class to make it working :
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "a"),
#JsonSubTypes.Type(value = SubClassB.class, name = "b")
})
Thanks a lot for all your answers.

Resources