Spring Boot unmarshalling JSON request body creates modifiable list - spring-boot

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

Related

GET method: How to convert snake_case query string to camelCase DTO

I use snake_case DB columns and camelCase DTO.
And our team want to use snake_case when we code React component.
Because of it, I added #JsonNaming on DTO. But it works when I send Json data, as you know.
Is there any annotation or setting similar to #JsonNaming?
Here is my postman data and sample codes.
Debug data: sampleName=name, sampleDesc=null.
// Controller
#RestController
#RequestMapping("/sample")
public class SampleController {
#Autowired
private SampleService sampleService;
#GetMapping
public Result getSampleList(SampleDTO param) throws Exception {
return sampleService.getFolderList(param);
}
#PostMapping
public Result insertSample(#RequestBody SampleDTO param) throws Exception {
// this method works well with #JsonNaming
return sampleService.insertFolder(param);
}
}
// DTO
#Setter
#Getter
#NoArgsConstructor
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
#Alias("SampleDTO")
public class SampleDTO {
#NotNull
private Long sampleNo;
#NotBlank
private String sampleName;
private String sampleDesc;
#Builder
public SampleDTO(Long sampleNo, String sampleName, String sampleDesc) {
this.sampleNo = sampleNo;
this.sampleName = sampleName;
this.sampleDesc = sampleDesc;
}
}
I had the same problem and didn't find an annotation for this but maybe you can use #ConstructorProperties like this in your DTO's constructor:
#ConstructorProperties({"sample_no","sample_name","sample_desc"})
public SampleDTO(Long sampleNo, String sampleName, String sampleDesc) {
this.sampleNo = sampleNo;
this.sampleName = sampleName;
this.sampleDesc = sampleDesc;
}

Spring boot consume 2 rest and merge some fields

Im new to Spring Boot and got a problem were i need to consume 2 remote Rest services and merge the results. Would need some insight on the right approach.
I got something like this:
{"subInventories":[
{"OrganizationId": 0,
"OrganizationCode":"",
"SecondaryInventoryName":"",
"Description":""},...{}...],
{"organizations":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":,
"ManagementBusinessUnitName":""}, ...{}...]}
and need to make it into something like this:
{"items":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":0,
"ManagementBusinessUnitName":"",
"SecondaryInventoryName":"",
"Description":""},...{}...]
got 2 #Entitys to represent each item, Organizations and Inventories with the attributtes like the JSON fields.
EDIT
Currently trying to get matches with Java8 stream()
#GetMapping("/manipulate")
public List<Organization> getManipulate() {
List<Organization> organization = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) getSuvInventories();
List<Organization> intersect = organization.stream().filter(o -> subInventories.stream().anyMatch(s -> s.getOrganizationId()==o.getOrganizationId()))
.collect(Collectors.toList());
return intersect;
}
found this searching but i got many classes and I don't know if it would be better to just for each organization get the subinventories and put them in a list of maps like
List<Map<String,Object> myList = new ArrayList<>();
//Loops here
Map<String,Object> a = new HashMap<>();
a.put("OrganizationID", 1231242415)...
myList.add(a)
Quite lost in what the right approach is.
EDIT2
Here the classes I'm using.
Organizations
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Organization implements Serializable{
//#JsonObject("OrganizationId")
#Id
private Long OrganizationId;
private Long ManagementBusinessUnitId;
private String OrganizationCode,OrganizationName,ManagementBusinessUnitName;
public Organization() {
}
//getters setters
}
SubInventories
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubInventories implements Serializable{
#Id
private Long OrganizationId;
private String OrganizationCode,SecondaryInventoryName,Description;
public SubInventories() {
}
//getters and setters
}
Wrapper to unwrapp consume
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wrapper {
//#JsonProperty("items")
private List<Object> items;
public Wrapper() {
}
public List<Object> getOrganization() {
return items;
}
public void setOrganization(List<Object> organization) {
this.items = organization;
}
}
OrganizationController
#RestController
public class OrganizationController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/organizations")
public List<Object> getOrganization() {
return getOrganizationInfo();
}
private List<Object> getOrganizationInfo() {
String url = env.getProperty("web.INVENTORY_ORGANIZATIONS");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
builder.queryParam("fields", "OrganizationId,OrganizationCode,OrganizationName,ManagementBusinessUnitId,ManagementBusinessUnitName");
builder.queryParam("onlyData", "true");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> temp = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = temp.getBody().getOrganization();
return data;
}
}
SubInventoryController
#RestController
public class SubInventoryController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/sub")
public List<Object> getSuvInventories() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("this is private :(");
builder.queryParam("onlyData", "true");
builder.queryParam("expand", "subinventoriesDFF");
builder.queryParam("limit", "999999");
builder.queryParam("fields", "OrganizationId,OrganizationCode,SecondaryInventoryName,Description");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
headers.set("REST-Framework-Version", "2");
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> subInventories = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = subInventories.getBody().getOrganization();
return data;
}
}
where I'm right now
#RestController
public class MainController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#Autowired
private OrganizationController organizationController;
#Autowired
private SubInventoryController subInventoryController;
#GetMapping("/manipulate")
public Map<Organization, List<SubInventories>> getManipulate() {
List<Organization> organizations = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) subInventoryController.getSuvInventories();
Map<Organization,List<SubInventories>> result = new HashMap<Organization,List<SubInventories>>();
for(Organization organization : organizations) {
List<SubInventories> subInventoryMatched = (List<SubInventories>) subInventories.stream().filter( s -> s.getOrganizationId()== organization.getOrganizationId()).collect(Collectors.toList());
result.put(organizations.get(0), subInventoryMatched);
}
return result;
}
}
From what I understand I need to make a wrapper class for each POJO cause the response looks like this
/organizations
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "adasd",
"OrganizationName": "Hotel Bahía Príncipe Sunlight Costa Adeje",
"ManagementBusinessUnitId": 131231,
"ManagementBusinessUnitName": "asdasfdas"
},
{
"OrganizationId": 2,
"OrganizationCode": "adadas",
"OrganizationName": "Hadasd",
"ManagementBusinessUnitId": 1231,
"ManagementBusinessUnitName": "aewfrqaew"
}]}
and /subInventories
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asada",
"SecondaryInventoryName": "adfasdfasdgf",
"Description": "pub"
},
{
"OrganizationId": 2,
"OrganizationCode": "asgfrgtsdh",
"SecondaryInventoryName": "B LOB",
"Description": "pub2"
}
]}
If used the generic one with Object I get a java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.demo.model.Organization in the stream().filter and for the merge of the fields another class to get the desired
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asdas",
"OrganizationName": "adsadasd",
"ManagementBusinessUnitId": 1,
"ManagementBusinessUnitName": "asdasdf",
"SecondaryInventoryName": "sfsdfsfa",
"Description": "pub1"
}]}
Tons of classes if i get lots of POJO
I assume the following from the information you provide:
You have two Datatypes (Java classes). They should be merged together to one Java class
You have to load this data from different sources
Non of the classes are leading
I can provide you some example code. The code is based on the previos adoptions. This will give you an idea. It's not a simple copy and paste solution.
At first create a class with all fields you want to include in the result:
public class Matched {
private Object fieldA;
private Object fieldB;
// Some getter and Setter
}
The Basic idea is that you load your data. Than find the two corresponding objects. After that do your matching for each field.
public List<Matched> matchYourData() {
// load your data
List<DataA> dataAList = loadYourDataA();
List<DataB> dataBList = loadYourDataB();
List<Matched> resultList = new ArryList<>();
for (dataA: DataA) {
DataB dataB = dataBList.stream()
.filter(data -> data.getId() == dataA.getId())
.findFirst().orElseThrow();
// Now you have your data. Let's match them.
Matched matched = new Matched();
matched.setFieldA(dataB.getFieldA() == dataA.getFieldA() ? doSomething() : doSomethingElse());
// Set all your fields. Decide for everyone the matching strategy
resultList.add(matched);
}
return resultList;
}
This is a quite simple solution. Of course you can use Tools like Mapstruct for mapping purpose. But this depends on your environment.

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 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.

Values in the response of api are empty

ResponseInJsonFormat is a class,
#Builder
#Getter
public class ResponseInJsonFormat {
// Id field in Eloqua
private String id;
// List of field values
private List<FieldValues> fieldValues;
}
And FieldValues is also a class defined as,
#Builder
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class FieldValues {
// id of the parameter
private String ID;
// parameters
private String type;
// Value :- value of the parameter
private String value;
}
I'm writing an api ,
#RequestMapping(value = "/service/XYZService", method = RequestMethod.POST)
public ResponseInJsonFormat sendData(#RequestParam String a) {
FieldValues parameter = FieldValues.builder()
.ID(FIELD_ID)
.type(FILED_TYPE)
.value(a)
.build();
List<FieldValues> values = Arrays.asList(parameter);
ResponseInJsonFormat response = ResponseInJsonFormat .builder()
.fieldValues(values)
.id(ID)
.build();
// need to return ResponseInJsonFormat
return response
}
But when I call to a service it returns something like,
{
"id": "110",
"fieldValues": [
{}
]
}
Could somebody assist about this empty json object of fieldValues.
I havnt understand why to put #JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
if I dont use this annotation I get an error as,
no properties discovered to create BeanSerializer
So I used the annotation, the error has gone but getting empty value for the same, even though the value is explicitly assigned.
I do not understand why to do all this it just being a list.
Is there any document I should read about,please advice.
If I'm not wrong, spring return the response in JSON format. Then why to do all this.

Resources