How to change spring resources list name - spring

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.

Related

Spring data elasticsearch embedded field mapping

I'm struggling with mappings some field. I looked for an answer but couldn't find anything solving my case. Let's cut to the chase.
I have my document class
#Doucment
public class DocumentClass {
#Field(type = FieldType.Nested)
private EmployeeId employeeId;
}
An EmployeeId is wrapper for my uuid identifier. This object has nothing but just getters and setters and jackson annotations. The thing is that object extends some base class so such objects like EmployeeId can inherit this object. This super class has field id and this causes the problem. When I post some data to elasticsearch then it looks like this:
{
"employeeId": {
"id": "someUUID"
}
}
But I want to map this to be like:
{
"employeeId": "someUUID"
}
I wonder if there is a way to flatten this object.
If I get it right, you want to convert your EmployeeId class to a String and back. You have 2 possibilities to do that:
Using a property converter
If you only want to convert an EmployeeId in this entity and might keep it as it is in another, you should use a property converter that is only registered for this property:
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
public class EmployeeIdConverter implements PropertyValueConverter {
#Override
public Object write(Object value) {
return value instanceof EmployeeId employeeId ? employeeId.getId() : value.toString();
}
#Override
public Object read(Object value) {
return new EmployeeId(value.toString());
}
}
This converter must be registered on the property, notice that the field type is set to Keyword as it probably should not be analysed:
import org.springframework.data.elasticsearch.annotations.ValueConverter;
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
#ValueConverter(EmployeeIdConverter.class)
private EmployeeId employeeId;
}
Using a global converter
If you are using this EmployeeId at several places you might want register globally 2 converters for the two conversion directions:
#WritingConverter
public class EmployeeIdToString implements Converter<EmployeeId, String>{
#Nullable
#Override
public String convert(EmployeeId employeeId) {
return employeeId.getId();
}
}
#ReadingConverter
public class StringToEmployeeId implements Converter<String, EmployeeId>{
#Nullable
#Override
public EmployeeId convert(String id) {
return new EmployeeId(id);
}
}
To register these, you need to provide a custom client configuration (see the documentation):
#Configuration
public class MyClientConfig extends ElasticsearchConfiguration {
#Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
}
#Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
Collection<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new EmployeeIdToString());
converters.add(new StringToEmployeeId());
return new ElasticsearchCustomConversions(converters);
}
}
In this case, only the field type needs to be adjusted
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
private EmployeeId employeeId;
}
I am guessing you have an Id field which is a string.
You need to put a #JsonValue annotation on that field to make jackson serialize it the way you want.
Field annotated by JsonValue will be used to serialize your pojo into json.
If your Id field is private, then add the annotation on the getter of that field.
https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonValue.html

Spring Boot unmarshalling JSON request body creates modifiable list

I am trying to create an immutable DTO.
Therefore I have added the #Builder and #Getter Lombok annotation for creating immutable objects from Pizza.class. To prevent the ingredients field to be initialized with a mutable List, I have added the #Singular Lombok annotation.
DTO
#Builder
#Getter
public class Pizza {
private final String name;
#Singular
private final List<String> ingredients;
}
Now if I create an API endpoint and try to send a pizza JSON to that endpoint, it somehow gets unmarshalled by Spring, but the result of that process is a mutable ingredient list.
Controller
#RestController
#RequestMapping("/api/v1/demo")
public class DemoController {
#PostMapping("/pizza")
Pizza addPizza(#RequestBody Pizza pizza) {
pizza.getIngredients().add("Honey");
return pizza;
}
}
Request/ Response
Request body:
{
"name": "Hawaii",
"ingredients": ["Pineapple"]
}
Response body:
{
"name": "Hawaii",
"ingredients": [
"Pineapple",
"Honey"
]
}
The below code snippet is throwing a java.lang.UnsupportedOperationException, which indicates to me that the ingredients field is an unmodifiable list.
Code snippet
var ingredients = new ArrayList<String>();
ingredients.add("Pinapple");
var pizza2 = Pizza.builder().name("Hawaii").ingredients(ingredients).build();
pizza2.getIngredients().add("Honey");
My questions:
How is Spring Boot doing the marshalling/ unmarshalling of the request body/ response body?
How can I prevent Spring Boot from initializing the ingredients field with a modifiable list?
Your list gets passed to a constructor in the builder, so it's overriding what #Singular is doing here. You can drop the Singular and Builder annotations, create your own builder, and deserialize through it. In the Pizza constructor, the list is made immutable.
#Getter
#JsonDeserialize(builder = Pizza.PizzaBuilder.class)
public static class Pizza {
private final String name;
private final List<String> ingredients;
private Pizza(String name, List<String> ingredients) {
this.name = name;
this.ingredients = Collections.unmodifiableList(ingredients);
}
#JsonPOJOBuilder
#Setter
#Getter
static class PizzaBuilder {
List<String> ingredients;
String name;
PizzaBuilder name(String name) {
this.name = name;
return this;
}
PizzaBuilder ingredients(List<String> ingredients) {
this.ingredients = ingredients;
return this;
}
public Pizza build() {
return new Pizza(name, ingredients);
}
}
}

How to Enable/Disable Entity Relations in Runtime?

I have a Spring Boot app that has basic CRUD services. For the read services I want to see their relations as well. There is no problem for implementing relations by #ManyToOne, #OneToOne, etc. annotations like this example.
My problem is I want to enable this relations based on a parameter in list service or I could use another endpoint as well. How can I achieve this? Any suggestions are welcome.
parameter version could be like ->
/employe/list?includeRelations=true
endpoint version could be like ->
/employee/list/byRelations
My entities are like;
#Entity
#Table(name = "employee")
public class Employee{
private long id;
private String name;
private Address address;
// getter setters
}
#Entity
#Table(name = "address")
public class Address {
private long id;
private String name;
private String postalCode;
// getter setters
}
EDIT
e.g.
without includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
}
with includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
"address": {
"id":1,
"name": "HOME",
"postalCode": "11111"
}
}
its some sudo code for your understanding . you can use Query Parameter and In Condition you call repo what you want :
for my scenario i want different response short, medium and long
#RequestMapping(value = "/getContacts", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json")
public String getContact(#RequestBody ContactItemRequestInfo contactItemRequestInfo,
#RequestParam(required = false) String key,
String Contact)
{
if(key.equals("medium"))
{
return Contact="{\"responseCode\":\"02\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else if(key.equals("long"))
{
return Contact="{\"responseCode\":\"03\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else
{
return Contact="{\"responseCode\":\"00\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
}
It will be helpful for you !!
One of the ways would be to have different data transfer objects to return, depending on the REST request.
Let's assume you have the following classes, apart from entities.
class EmployeeDto {
private Long id;
private String name;
}
class EmployeeAddressDto {
private Long id;
private String name;
private AddressDto address;
}
class AddressDto {
private Long id;
private String name;
private int postalCode;
}
Then in a controller you would do something like this.
#GetMapping("/employee/list")
public ResponseEntity<?> getEmployees(#RequestParam int detailed) {
if (detailed) {
return employeeService.getDetailedEmployeeList();
} else {
return employeeService.getEmployeeList();
}
}
Service inteface would look like this.
interface EmployeService() {
List<EmployeeDto> getEmployeeList();
List<EmployeeAddressDto> getDetailedEmployeeList();
}
You would also need to handle entities to transfer objects conversions.
You can annotate the relations with fetchType=Lazy . Then invoke the getters to manually load the needed relations.
Another option is to eagerly load all relationships, but annotate the response with #JsonView and exclude the relations you don't need.

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.

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