Spring MVC + Jackson - JsonView - java

I need to expose two different set of values from my model, so i implemented 2 views
public class Views {
public static class Small{ }
public static class Large extends Small { }
}
Then, in my model i put (all other fields are annotated with JSONIgnore
#JsonView(Views.Small.class)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id_posto", unique = true, nullable = false)
public int getIdPosto() {
return this.idPosto;
}
public void setIdPosto(int idPosto) {
this.idPosto = idPosto;
}
#JsonView(Views.Large.class)
#NotNull
#Column(name = "nome_posto_park")
public String getNomePosto() {
return this.nomePosto;
}
public void setNomePosto(String nomePosto) {
this.nomePosto = nomePosto;
}
On my Controllers I have 2 methods:
#RequestMapping(value = "/spots", method = RequestMethod.GET)
public ResponseEntity<Posto> getSpotStatus(#RequestParam(value = "idPosto") int idPosto,
#RequestParam(value = "occupied") boolean occupied) {
Posto posto = postoService.findByIdPosto(idPosto);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
mapper.setConfig(mapper.getSerializationConfig()
.withView(Views.Small.class));
mapper.convertValue(posto, JsonNode.class);
return new ResponseEntity<Posto>(posto, HttpStatus.OK);
and
#RequestMapping(value="/spot", method = RequestMethod.GET)
public ResponseEntity<List<Posto>> getSpotList(#RequestParam (value = "idPiano") int idPiano){
Piano piano = pianoService.findById(idPiano);
List<Posto> posti = postoService.showSpotsByFloor(-1, piano);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
mapper.setConfig(mapper.getSerializationConfig()
.withView(Views.Large.class));
mapper.convertValue(posti, JsonNode.class);
return new ResponseEntity<List<Posto>>(posti, HttpStatus.OK);
}
Che result is the same... (obviously the first is a single Posto and the second a List but all the fields from the model are serialized....
What I'm doing wrong when using views?

You need define produces and consume with agree view and annotation with #ResponseBody
Example: Change your needed
#Produces(value = { MediaType.APPLICATION_JSON_VALUE })
#Consumes(value = { MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody public ResponseEntity<List<Posto>> getSpotList(...
//when request, put your client agree request view
protected HttpEntity<T> headers()
{
final HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
headers.set("application", MediaType.APPLICATION_JSON_VALUE);
// T define your type here
return new HttpEntity<T>(headers);
}

Your problem is that Spring instantiates its own Jackson ObjectMapper when the ApplicationContext starts.
You'll have to autowire the Spring managed ObjectMapper and configure that instance instead of creating your own with new.
private final ObjectMapper mapper;
public MyController(ObjectMapper mapper) {
this.mapper = mapper;
}

Related

Using params in GetMapping in Spring results in ambiguous handler method for multiple parameters

I have the following REST endpoints in Spring boot
#GetMapping(value = "students", params = {"name"})
public ResponseEntity<?> getByName(#RequestParam final String name) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
#GetMapping(value = "students", params = {"tag"})
public ResponseEntity<?> getByTag(#RequestParam final String tag) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
The above handlers work fine for the following requests:
localhost:8080/test/students?name="Aron"
localhost:8080/test/students?tag="player"
However, whenever I try the following:
localhost:8060/test/students?name="Aron"&tag="player"
it throws java.lang.IllegalStateException: Ambiguous handler methods mapped and responds with an HTTP 500
How can I change this behavior? I want my app to respond only when I get either a tag query parameter or a name query parameter.
For anything else, I want it to ignore even if it's a combination of two parameters.
Why is it throwing the ambiguous error here and how can we handle that?
You can use #RequestParam(required = false):
#GetMapping(value = "students")
public ResponseEntity<?> get(
#RequestParam(required = false) final String name,
#RequestParam(required = false) final String tag) {
if ((name == null) == (tag == null)) {
return new ResponseEntity<>(false, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(true, HttpStatus.OK);
}
it seems you can use negations in params. Something like:
#GetMapping(value = "students", params = {"name", "!tag"})
public ResponseEntity<?> getByName(#RequestParam final String name) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
#GetMapping(value = "students", params = {"tag", "!name"})
public ResponseEntity<?> getByTag(#RequestParam final String tag) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
References: Advanced #RequestMapping options

How to ignore the #JsonProperty value while using the #RequestBody in controller to map value to DTO

I want to MAP my HTTP request parameter value directly to my DTO USING #JsonProperty on the basis of the variable name not by #JsonProperty value. I am not able to map the value to DTO because it's expecting request value according to the JsonProperty name. Is there anyway to disable #JsonProperty value while using the #RequestBody ?
JSON send by frontend:
{
"userId":"1",
"payMethod":"payMethod"
}
MyDto.class
public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
//getter setter
}
MyController.class
public class MyController{
#RequestMapping(value = "payment", method = RequestMethod.PUT)
public Integer PaymentUpdate(#RequestBody final MyDto myDto) throws JsonProcessingException {
}
you can do this by using multiple setter method for that DTO method. For example
Payload:
{
"userId":"1",
"payMethod":"payMethod"
}
then
MyDto.class public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
add one more setter relevant to the required variable name in the DTO class.
#JsonSetter("specifiedName")
void setUserId(String userId){
this.userId=userId
}
void setPayMethod(String payMethod){ // Will work for "BETAALMETHODE" variable name
this.payMethod=payMethod
}
#JsonSetter("payMethod")
void setPayMethod(String payMethod){
this.payMethod=payMethod
}
This will solve your problems and variable payMethod will assign in both the cases.
You can use JacksonMixin during csv parsing:
public abstract class MyDtoMixin {
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
}
ObjectMapper mapper = new ObjectMapper(); // or CsvMapper mapper = new CsvMapper();
mapper.addMixInAnnotations(MyDto.class, MyDtoMixin.class);

JSON conversion error from java Pojo

I have userDTO like below, and
I am trying to convert userDTO to json string and calling rest API endpoint from my Controller, but the called rest API end point throws error as "data" is not valid JSONobject
public class UserDTO {
private String userId;
private String firstname;
private String lastname;
private List<Order> orders;
some more data member plus // setter / Getters
}
My controller class:- [converting userDTO to json string]
public class OrderController {
UserDTO userRecord = new UserDTO ();
// userRecord some values here
final HttpStatus httpStatus;
HttpEntity<?> httpEntity;
final HttpHeaders headers = new HttpHeaders();
final ObjectMapper mapper = new ObjectMapper();
headers.setContentType(MediaType.APPLICATION_JSON);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
String jsonInput;
// I guess this is the point creating that issue. May be Im doing in wrong way....
jsonInput = mapper.writeValueAsString(new JSONObject().put("data",userRecord ));
httpEntity = new HttpEntity<Object>(jsonInput, headers);
// calling the rest API endpoint
ResponseEntity<String> responseEntity = restTemplate.exchange(
URL, HttpMethod.POST, httpEntity,
String.class,someId);
}
Server Sinippet:-
public MicroserviceResponse createOrder(#PathVariable("cId") final String cId, #RequestBody final String requestBody) throws Exception {
ObjectMapper mapper = new ObjectMapper();
requestJSON = new JSONObject(requestBody).getJSONObject("data");
final String jsonData = requestJSON.toString();
UserDTO orderSource = mapper.readValue(jsonData,
UserDTO .class);
}
Problem:-
Called API [server] throws "data is not valid JSONObject. Am i missing something here ? please guide me.
Trying to send below kind of JSON format
{
"data":{
"username":"test",
"orderId": "123097R",
"firstName":"xydz",
"lastName":"xyzd",
"email":"xx#gmail.com"
}
}
As there are no detailed server logs provided - I assume that the mentioned error is happening at the REST layer.
Please try this - create a payload Java class:
public class RestPayload implements java.io.Serializable {
private UserDTO data;
public UserDTO getUserDTO (){
return this.data;
}
public void setUserDTO(UserDTO data){
this.data = data;
}
}
And then modify your current rest operation to :
#POST
public MicroserviceResponse createOrder(#PathVariable("cId") final String cId, #RequestBody final RestPayload restPayload ) throws Exception {
UserDTO orderSource = restPayload.getUserDTO();
}
UPDATE:
You can also play with the raw JSON and modify the values according to your needs.
Try this - the below code shows how to add "data" as a parent object to the UserDTO :
Gson gson = new Gson();
JsonElement userDtoJsonElement = gson.toJsonTree(userDTO);
JsonObject dataObject = new JsonObject();
dataObject.add("data", userDtoJsonElement);
System.out.println(gson.toJson(dataObject));

MockMvc - calling a query with a complicated object

I want to send to the controller a complex object consisting of files and simple types.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I want to create a test to send an object, but I do not know how to finish it.
#Test
public void testCreatePhotoContribution() throws Exception {
ContributionNew<Photo> contribution = new ContributionNew<>();
MockMultipartFile multipartFile = new MockMultipartFile("photo", "C:\\Users\\Jonatan\\Pictures\\2.png",
"image/png", "Spring Framework".getBytes());
Photo.Builder photoBuilder = new Photo.Builder(
multipartFile
);
contribution.getElementsToAdd().add(photoBuilder.build());
mockMvc
.perform(post("/api/v1.0/movies/{id}/contributions/photos", 1)
.contentType(...)
.content(...))
.andExpect(status().isCreated());
}
I do not know how to send such an object as #ResuestBody? I do not know how to finish this test.
You can do something like this.
ObjectMapper = new ObjectMapper(); // You can also Autowire this
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mockMvc
.perform(post("/api/v1.0/movies/{id}/contributions/photos", 1)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(contribution)))
.andExpect(status().isCreated());

using Swagger #ApiResponse responseContainer not working when code is 400

In Swagger Java API, when I use a responsecontainer="List" (Or "Set") with a code=400, I am not getting the model of the response on Swagger-GUI. I am just getting Array[Object].
Here is the concrete case:
#CrossOrigin
#RestController
#RequestMapping(value = "/api")
#Loggable(prepend = true, trim = false)
public class ConfigResource {
private final ConfigResourceDelegate delegate;
#Inject
public ConfigResource(final ConfigResourceDelegate delegate) {
this.delegate = delegate;
}
#RequestMapping(
value = "/v1/config",
method = PUT,
consumes = APPLICATION_JSON_UTF8_VALUE,
produces = APPLICATION_JSON_UTF8_VALUE
)
#ApiResponses(value = {#ApiResponse(code=202,message = "ACCEPTED" ),
#ApiResponse(code=200,response = Rejection.class, responseContainer
= "Set", message = "BAD_REQUEST"),
#ApiResponse(code=500, message = "INTERNAL_SERVER_ERROR")})
public ResponseEntity<?> putConfig(final #RequestBody ConfigDto
configDto){
return delegate.putConfig(riskConfigDto);
}
}
Here is the Rejection Class:
public class Rejection {
private Long id;
private RejectionDTO rejection;
private String originMessage;
public Rejection() {
}
public Long getId() {
return id;
}
public RejectionDTO getRejection() {
return rejection;
}
public String getOriginMessage() {
return originMessage;
}
public void setId(Long id) {
this.id = id;
}
public void setRejection(RejectionDTO rejection) {
this.rejection = rejection;
}
public void setOriginMessage(String originMessage) {
this.originMessage = originMessage;
}
}
So normally i'am supposed to have this model between [] in the swagger UI. However, I am getting Array[Object]:
See screen capture
To make your example work, you need to change your return value from wildcard, ResponseEntity<?>, to a concrete class, ResponseEntity<List<Rejection>>. Also, you need to change responseContainer to a List from Set.
#RequestMapping(
value = "/v1/config",
method = PUT,
consumes = APPLICATION_JSON_UTF8_VALUE,
produces = APPLICATION_JSON_UTF8_VALUE
)
#ApiResponses(value = {#ApiResponse(code=202,message = "ACCEPTED" ),
#ApiResponse(code=200,response = Rejection.class, responseContainer
= "List", message = "BAD_REQUEST"),
#ApiResponse(code=500, message = "INTERNAL_SERVER_ERROR")})
public ResponseEntity<List<Rejection>> putConfig(final #RequestBody ConfigDto
configDto){
return delegate.putConfig(riskConfigDto);
}

Categories

Resources