Converting inbound JSON message to Java Object with Spring - java

I'm trying to create a Java object by deserialising a JSON message in Spring Boot. I have a class as such:
public class Status implements Serializable {
private int uptime;
}
Then I have a Rest Controller as such:
#RestController
public class StatusReceiver {
#RequestMapping(value = "/poststatus", method = RequestMethod.POST)
public #ResponseBody Status storeStatus(#RequestBody Status statusMessage) {
System.out.println("Uptime: " + statusMessage.getUptime());
return statusMessage;
}
}
I definitely don't understand a fair bit of what I'm doing here. I want the controller to be creating a Status object when it receives the message and have it store it in the database.
Output of this code is:
Uptime: 0
The message it's receiving is:
{
"Status": [
{
"uptime": 12345.0
}
]
}
I'm obviously missing something. How can I have Spring convert the JSON object into the Status java object? Also where is the return from the method meant to be going and doing?

The request body JSON is not matching the Status object structure. Either your body should be
{uptime: 12345.0}
or the Status class should be
class Status {
List<Map<String, Long>> status = new ArrayList<>();
}
Instead of having a List of Map, you can define a new class and have a list of this new class
class StatusInfo{
long uptime;
}
class Status {
List<StatusInfo> status = new ArrayList<>();
}

Related

Java #requestBody doesn't work, dto empty

For some reason java can't map DTO with requestBody and all values are default ones, as for request it works, with payload for ex. "{"productId":1,"commitment":6,"returnMonths":"2"}"
DTO
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
// contructers
}
Controller :
#PostMapping(value = "/calculate", consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String calculatePrice(#RequestBody Request request) {
productService.calculatePrice(request);
return "Success";
}
front request:
submit: async function() {
let request = {
productId: this.productSelected,
commitment: this.optionSelected,
returnMonths: this.input
};
let data = await getCalculation(request);
console.log(data);
}
DTO maps as:
productId : 0
commitment : 0
returnMonths : null
Tried an exact copy of your code and it worked when tested with Postman. This makes me think it's either something to do with the FE or maybe some issue in the service. I'd check if the Frontend really sends the data.
Try to annotation Request class with #AllArgsConstructor like:
#AllArgsConstructor
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
}
If your request body contains properties that is date such as LocalDateTime, make sure to format it in your DTO using #JsonFormat(pattern="") respecting the input value.

Spring MVC - The request sent by the client was syntatically incorrect

I have a simple controller:
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<MyResponse> print(#RequestBody final RequestModel request) throw ApiException {
return null;
}
And in my RequestModel:
class RequestModel {
private String name;
private CustomData data;
}
CustomData:
class CustomData {
private String data;
}
When I make POST request without the "data" field, it works. But if I add the "data" field, I'm getting a 400, The request sent by the client was syntatically incorrect.
O dont know If you wrote all the code, but tou should implements serializable and write setters and getters.
But, answering your question, you should annotate your fields with #JsonProperty to specify the required flag.
Your posted JSON should be something like this :
{
"name":"Luke",
"data": {
"data":"I am your father"
}
}
OBS: if you are using Postman, please set the header : key: Content-Type, value: application/json
You should specify an endpoint:
Example :
#PostMapping("/data")
Instead of
#RequestMapping(method = { RequestMethod.POST })
If you are using default port, try again the post to :
http://localhost:8080/data
OBS: RequestModel and CustomerData must have getters and setters.

JSON Error on clientside when sending ResponseEntity from serverside after post request

I have an angular/spring boot webapp. When I send a create user postrequest the angular clientside app isn't able to read the body of the response entity that I send back after the operation. The error is:
{error: SyntaxError: Unexpected token U in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttp…, text: "User successfully created."}
I know that this is caused because the body content isn't in JSON format. But the error persists even when I add produces = "application/json" as an attribute to the #PostMapping annotation.
Heres the code:
#RestController
#RequestMapping("api/user")
public class UserController {
private final Log logger = LogFactory.getLog(this.getClass());
#Autowired
UserService userService;
#Autowired
UserDao userDao;
#PostMapping(path = "/create", produces = "application/json")
private ResponseEntity<String> createNewUser(#RequestBody UserCreateDTO newUser) {
logger.info("name is: " + newUser.getUserName());
Status status = userService.createUser(newUser);
return ResponseEntity.status(status.isSuccess() ?
HttpStatus.CREATED : HttpStatus.BAD_REQUEST).body(status.getInfo());
}
What should I do to solve this problem? I think it has something to do with the use of ResponseEntity. I could just send the status DTO object that I've made back instead, but I want to be able to manipulate the httpStatus code that is being sent back too, so that's why I want to use the ResponseEntity instead.
It looks like you're returning a string literal instead of a json object. The returning object when converted to json should be like
{
"status": "user created successfully"
}
try returning your full status object instead of status.getInfo() then your return object should look something like:
{
"info": "user created successfully"
}
and you can call status.info within your javascript to reference the return
and will have to change your return type to RepsonseEntity<Status>
In fact yes you are using ResponseEntity but with a String as body, because you are using:
.body(status.getInfo());
You need to specify an object in the body, you can create a POJO that will hold the message for you, wraps the status.getInfo() String, and it will be read as JSON.
The message POJO class:
public class MessageObject {
private String message;
//Constructors, getter and setter
}
Your return code would be:
return ResponseEntity.status(status.isSuccess() ?
HttpStatus.CREATED : HttpStatus.BAD_REQUEST).body(new MessageObject(status.getInfo()));

Why my parameters are not getting passed to the RESTful web service?

I have this code using Angular 4 HttpClient, supposed to communicate with a simple RESTful JAVA web service and get a JSONstring back:
this.client.post('http://localhost:8080/ToDoService/rest/service/create', {id: 'foo', todo: 'bar'}).subscribe((data) => {
console.log("Got data: ", data);
}, (error) => {
console.log("Error", error);
})
It's not working. What I mean is that the id and todo parameters are not getting passed to the REST backend.
At the other hand, if I change above code to:
this.client.post('http://localhost:8080/ToDoService/rest/service/create?id=foo&todo=bar', '').subscribe((data) => {
console.log("Got data: ", data);
}, (error) => {
console.log("Error", error);
})
Everything works fine, but I'm sure the second snipped is wrong. It just looks wrong.
Could you give me a push and point my mistake?
P.s
The JAVA backend:
#Path("/service")
public class Rest extends Application{
#POST
#Path("/create")
public Response printMessage(#QueryParam("id") String userId, #QueryParam("todo") String toDo) {
JSONObject result = new JSONObject();
result.put("id", userId);
result.put("todo", toDo);
return Response.status(200).entity(result.toString()).build();
}
}
You're mapping QueryParams, you need to map that payload to either a Map or an Object:
class PayLoad {
private String id;
private String todo;
// Getter and Setters
}
#Path("/service")
public class Rest extends Application{
#POST
#Path("/create")
public Response printMessage(#RequestBody PayLoad payload) {
JSONObject result = new JSONObject();
result.put("id", payload.getId());
result.put("todo", payload.getTodo());
return Response.status(200).entity(result.toString()).build();
}
}
Hope this helps!
First of all, looking at the MyKong tutorial you can see that #QueryParam accepts parameters sent in the URL. That is a first problem here. Back to the main point.
I am not an expert in Angular 4, but I think the problem lies deeper in your architecture. You are sending to your backend:
{id: 'foo', todo: 'bar'}
and expect in your Java:
#QueryParam("id") String userId, #QueryParam("todo") String toDo
You pass an object and expect in your Java backend two strings. If you want to get your object, you might create this kind of class:
public class JsonWrapper {
private String id;
private String todo;
// Classic constructor and setters
}
Then, in your service:
#Path("/service")
public class Rest extends Application {
#POST
#Path("/create")
public Response printMessage(#RequestBody JsonWrapper wrapper) {
JSONObject result = new JSONObject();
result.put("id", wrapper.getId());
result.put("todo", wrapper.getTodo());
return Response.status(200).entity(result.toString()).build();
}
}
If the JsonWrapper does not work, I think a Map can do the trick, too.

Java how to store object of varying type

I am currently using Spring MVC and Hibernate to develop my web app. Still learning inner workings of java.
I find my self in a situation where I need to store data in a field that could accept different type of Objects along with some other strings of data. More precisely I want to create a ReturnObject that could hold messages, error code...etc. This way my return JSON could stay consistent through out the api.
This is how I created my ReturnObject
public class ReturnResponse {
//Set fields
private Object returnObj; <----- Need this to accept different types
private HttpStatus httpStatus;
private String message;
private String developerMessage;
// Start build
public ReturnResponse(){
this.returnObj = returnObj;
this.httpStatus =.....etc.
}
// Setters... getters...
}
private Object returnObj; so that this field could accept Collection, Maps, Class..etc but is this safe?
I seem to remember that its always a good practice to declare specific object type in the field to ensure type-safe.
Questions
Do you see foreseeable problem in the future, is there a better way of doing this?
If this is not type safe, how would I make it safer.
You can use a generic :
public class ReturnResponse<ObjectType> {
private ObjectType returnObj;
private HttpStatus httpStatus;
private String message;
private String developerMessage;
public ReturnResponse(ObjectType returnObj, /*other parameters...*/) {
this.returnObj = returnObj;
// set other parameters
}
public ObjectType getReturnObj() {
return this.returnObj;
}
// ...
}
It will work if you know when you instanciate your ReturnResponse what's the type of the returned object.
I use this pattern in most of my API without problem.
If you want "to store data in a field that could accept different type of Objects along with some other strings of data." then you need to have a base class for that object which in your case will probably be Object.
The problem is that you need to have some way to decipher later in your code, what type of object that is - which in most cases I think will be undesirable and will require unsafe casting.
Only way I can think of to make it more safe is to make some kind of wrapper like:
public class Bean {
private String string;
private Integer integer;
private List<String> list;
private Bicycle bicycle;
//setters
//...
public Optional<Bicycle> getBicycle() {
return Optional.ofNullable(bicycle);
}
//... and so on...
}
The error handler should be in the controller and it should respond an http error. That means a correct HTTP error status, and a desired error message. An error should not look like a successful request (No status code 200). It's an error. In your front end you should handle the http error response accordingly.
With spring this can be very easy a nicely done. Here is an example of an error handler of a project of mine. It's an own class with the annotation #ControllerAdvice. spring will automatically use that.
This class will automatically catch any unhandled exception that are defined with #ExceptionHandler and send in my case a ShortExceptionResponse that contains the type and the message of the exception thrown, with a correct Http error status defined with #ResponseStatus.
In your front end you can react accordingly to the different HTTP status error codes. It's nice and generic.
#ControllerAdvice
public class RestExceptionResponseHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SetRestController.class);
#ExceptionHandler(NoSuchElementException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public #ResponseBody
ShortExceptionResponse noSuchElementExceptionHandler(Exception ex) {
LOGGER.error("An error occured processing a rest request", ex);
return new ShortExceptionResponse(ex);
}
#ExceptionHandler(value = {EntityAlreadyExistsException.class})
#ResponseStatus(HttpStatus.FORBIDDEN)
public #ResponseBody
ShortExceptionResponse entityAlreadyExistsExceptionHandler(EntityAlreadyExistsException ex) {
LOGGER.debug("A rest request could not been process due an illegal state of the target entity", ex);
return new ShortExceptionResponse(ex);
}
#ExceptionHandler(value = {IllegalArgumentException.class, UnsupportedOperationException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public #ResponseBody
ShortExceptionResponse illegalArgumentExceptionHandler(Exception ex) {
LOGGER.error("An error occured processing a rest request", ex);
return new ShortExceptionResponse(ex);
}
#ExceptionHandler(value = {HttpMessageNotReadableException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public #ResponseBody
ShortExceptionResponse httpMessageNotReadableExceptionHandler(Exception ex) {
LOGGER.error("An error occured processing a rest request", ex);
if (ex.getCause() instanceof InvalidFormatException) {
return new ShortExceptionResponse(new InvalidValueException(((InvalidFormatException) ex.getCause()).getOriginalMessage()));
}
return new ShortExceptionResponse(ex);
}
...
...
}
In the actual controller you just keep throwing the exception and it will be handled by your error handler
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody
MetadataDTO createMetadata(#RequestBody MetadataDTO metaDataDTO) throws EntityAlreadyExistsException {
MetadataDTO result = metaDataService.createMetadata(metaDataDTO.getName(), metaDataDTO.getDescription(), metaDataDTO.getType());
return result;
}
You can create a 'model' class to store the full object to be converted to json:
#AllArgsConstructor //or make a constructor with all the fields manually
class ResponseObject {
User user;
House house;
Car car;
}
Since you are using Spring, you already have the Jackson library. So you can do:
#Autowired ObjectMapper objectMapper; // no need to configure anything to use this
public String getJson(){
User user = new User("user", "password");
House house = new House(4, true, ...);
Car car = new Car();
ResponseObject resp = new ResponseObject(user, house, car);
String json = null;
json = objectMapper.convertValue(resp, ResponseObject.class);
// or:
try {
json = objectMapper.writeValueAsString(resp);
} catch (Exception e) {
...
}
// or: (would need to use a google Gson dependency)
Gson gson = new Gson();
json = gson.toJson(resp, ResponseObject.class);
return json;
}
Alternatively, if you really need the flexibility,
#Autowired ObjectMapper mapper;
public void printJson() {
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("number", 5);
jsonMap.put("String", "string");
jsonMap.put("kanky", 987L);
String json = mapper.writeValueAsString(jsonMap);
System.out.println("json: " + json);
} // works fine if your map values have a toString defined

Categories