I'm receiving an id (integer) and a executor (String) in my controller (Rest API). However, when looking at my database, I see that the string is being inserted into the database as an object. Example of database entry:
{
"executor": "Pietje"
}
Controller:
#PostMapping(path = "/accept/{id}")
public String acceptAssignment(#Valid #PathVariable Integer id, #RequestBody String executor) {
return assignmentService.acceptAssignment(id, executor);
}
Service implementation:
#Override
public String acceptAssignment(Integer id, String executor) {
Assignment assignment = assignmentRespository.findById(id).orElse(null);
assignment.setExecutor(String.valueOf(executor));
AssignmentDTO assignmentDTO = assignmentConverter.convertEntityToDto(assignment);
assignmentRespository.save(assignment);
return assignmentDTO.getExecutor();
}
What am I doing wrong, and how can I fix it?
I could pass along the entire DTO instead of just the 'executor' value, but that doesn't seem efficient. As far as I know, the problem is not with the frontend but I could add the React code if necessary.
TL;DR - you're using a String containing a JSON-object as if it was an attribute of this JSON-object. The solution it to treat this JSON properly.
Note that you don't need to mess with desirialization manually, let the JSON-converter of the framework do its job.
All that you need is a simple POJO:
#Getter
#Setter
public class AssignmentExecutor {
private String executor;
}
The above POJO can be automatically translated in & to the following of JSON without any effort from your side (owing to the magic of Spring):
{
"executor": "Pietje"
}
It would be automatically parsed to the proper type by a Spring's message-converter, you only need to specify that you need an AssignmentExecutor instead of a plain String.
#PostMapping(path = "/accept/{id}")
public String acceptAssignment(#Valid #PathVariable Integer id,
#RequestBody AssignmentExecutor executor) {
return assignmentService.acceptAssignment(id, executor);
}
Note
Introducing this new type would not require any changes in the Assignment, executor can still be represented as a String field.
By invoking orElse(null) on the optional result, you're creating a potential problem by depriving the possibility to get a meaningful exception if the data that corresponds to the given id was not found. In such a case, your current code would trigger a NullPointerException right on the next line. Instead, I would advise providing a suitable exception via Optional.orElseThrow().
A now again all that you need is to return an instance of AssignmentExecutor and it would be automatically converted into JSON:
#Override
public String acceptAssignment(Integer id, AssignmentExecutor executor) {
Assignment assignment = assignmentRespository.findById(id)
.orElseThrow(() -> new MyException("Assignment with id " + id + " was not found"));
assignment.setExecutor(executor.getExecutor());
assignmentRespository.save(assignment);
return executor;
}
You can use ObjectMapper to fetch the desired key-value from the #RequestBody String executor as:
Approach Here:
The #RequestBodythat you are getting from the API is in the object form and using ObjectMapper , it will be converted into a Map of attributes as in the API request and we can fetch the desired key-value pair.
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> map = mapper.readValue(executor, new TypeReference<>() {});
String s = (String) map.get("executor");
Added in acceptAssignment method as:
#Override
public String acceptAssignment(Integer id, String executor) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> requestMap = mapper.readValue(executor, new TypeReference<>() {});
Assignment assignment = assignmentRespository.findById(id).orElse(null);
assignment.setExecutor((String)requestMap.get("executor"));
AssignmentDTO assignmentDTO = assignmentConverter.convertEntityToDto(assignment);
assignmentRespository.save(assignment);
return assignmentDTO.getExecutor();
}
Related
I want accept a request body of the post request as User object in routes. I am using Play version 2.7.1
POST /user/create controllers.UserController.createUser(user : User)
My User object looks like this.
public class User{
#NotNull
String userId;
#NotNull
String userName;
#NotNull
#NotEmpty
String userCity;
.
.
.
}
My createUser method looks like this.
public Result createUser(User user){
//do smething with user
}
When I compile, I get an error saying that not found: type User
The reason I wanted to accept this way is to validate the request object user using hibernate validations, which look like the below code
public Result createUser(#Valid User user) {
//do smething with user
}
I have already looked into PathBindable and QueryStringBindable. Both of them are not useful to me , as user is not a path paramter or query parameter, but a post request
In the end, I want the framework to convert the request body into the User object and take care of validations, instead of writing code for converting the Json object into User object using Jackson library and validate the User object using Javax Validator.
Currently I am using these below functions for Deserialization and Validation
public static <T> T deserialize(String json, TypeReference type) throws IOException {
ObjectMapper mapper = new ObjectMapper();
T bean = mapper.readValue(json, type);
validateBean(bean);
return bean;
}
public static <T> void validateBean(T bean) throws IOException {
for (ConstraintViolation violation : beanValidator.validate(bean)) {
throw new IOException(violation.getPropertyPath().toString() + " " + violation.getMessage());
}
}
First of all you are mixing query parameters and request body.
All parameters that you pass from routes files are query parameters and controllers.UserController.createUser(user : User) will try to get your User from query params but it can happen only with help of PathBindable. As you don't pass user from query your routes will look like:
POST /user/create controllers.UserController.createUser()
And in your controller you will read POST body and parse json that you received:
public Result createUser(Http.Request request) {
JsonNode json = request.body().asJson();
// Convert to java object and do what you want
return ok("It works");
}
Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}
I am creating an API (written in Java) which I am deploying through serverless which ports to a AWS Lambda function. All aspects of the API function great except for the fact that the requests which are returned include the '\' character in front of all quotes.
To put this into perspective, I have a person class which contains instance variables for name (String) and mood (String). I then have my class which uses this class to get and create a Person object, and then Jackson is used to parse this into JSON format. This is what is returned to the handler function (for lambda) and is displayed as the "object body".
public class Person{
String name;
String mood;
//getters and setters and constructor
}
Then, later on there will be something in a different class like
Person person = new Person("bob", "good");
Which would be passed into my method which is supposed to convert things to JSON:
private String convStrToJson(Person person) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(person);
return json;
}
If I were to print this in the output, I'd get something like:
{"name":"bob","mood":"good"}
Which is what I want and expect. However, when deployed and called via GET request, the result is:
"{\"name\":\"bob\",\"mood\":\"good\"}"
I've tried several strategies, including additions to the parsing method such as:
json = json.replace("\"", "");
Which removes the quotes fully from both outputs, or:
json = json.replace("\\","");
Which has no effect at all. I also tried both of these as replaceAll methods and that just messed things up even more. I'm not sure what else I can do to get rid of these '\' characters, I understand why they're there but I don't know how to stop that. Any assistance is appreciated.
Okay so I figured it out. Turns out serverless not only includes Jackson, but actually in the layout it creates for handling responses, the "setObjectBody" section will accept any kind of object and use Jackson to parse it to JSON. This is where I messed up. I assumed it would only accept Strings, which is where the double encoding was occurring. Now, if I pass in the Person object, serverless/Jackson handles it appropriately for me and the expected output is returned. I'll include code snippets below to better demonstrate this solution. Serverless creates a 'handler' class which has a template including a method called handleRequest. Once filled in, this class now looks like this:
public class GetStatusHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
private static final Logger LOG = Logger.getLogger(GetStatusHandler.class);
#SuppressWarnings("unchecked")
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
BasicConfigurator.configure();
LOG.info("received: " + input);
try {
Map<String, String> pathParameters = (Map<String, String>) input.get("queryStringParameters");
if(pathParameters == null) {
LOG.info("Getting details for all persons ");
PersonControl control = new PersonControl();
Person[] result = control.myGetHandler(context);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(result)
.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
.build();
}else {
String name = pathParameters.get("name");
LOG.info("Getting details for "+name);
PersonControl control = new PersonControl();
Person result = control.myGetHandler(name, context);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(result)
.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
.build();
}
}catch(Exception e) {
LOG.error(e, e);
Response responseBody = new Response("Failure getting person", null);
return ApiGatewayResponse.builder()
.setStatusCode(500)
.setObjectBody(responseBody)
.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
.build();
}
}
}
Not that when returning the ApiGatewayResponse (via builder), an object is simply passed in to the .setObjectBody method ('result') which serverless automatically converts to JSON for us. Thats it! No parsing to JSON necessary in the code.
The response can be a user defined object as below
class Handler implements RequestHandler<SQSEvent, CustomObject> {
public CustomObject handleRequest(SQSEvent event, Context context) {
return new CustomObject();
}
}
Sample code can be found here.
Just use the Google Gson java library that can be used to convert Java Objects into their JSON representation.
Gson gson = new Gson();
gson.toJson(person);
I have asked a similar question before: this one
Now I have a similar but different issue.
My Spring MVC controller model is a JSON payload with a defined set of attributes that, unfortunately, are not part of a class in my project.
E.g.
{
"userId" : "john",
"role" : "admin"
}
I need to treat userId and role as separate Strings.
I currently have two ways to declare the controller method
public ResponseObject mvc(#RequestBody MyCustomDTO dto){
String userId = dto.getUserId();
String role = dto.getRole();
}
public ResponseObject mvc(#RequestBody ModelMap map){
String userId = (String)map.get("userId");
String role = (String)map.get("role");
}
I have been asked to find a different implementation because 1) requires to create a custom DTO class for each combination of parameters (most cases need 1 named parameter, e.g. delete(productId)) and 2) involves an entity that is not strictly defined. Especially when dealing with lists, it can contain arbitrary values that need to be checked at runtime.
Spring MVC, as I have found, does not support resolving #ModelAttribute from a JSON request body. Am I doing something wrong or is it just Spring not doing it? Can I grab a specific property, be it a plain primitive or an entire POJO, from the Request Body into a method argument?
In the second case it would be better to request a useful feature to Spring developers.
Spring version is 4.2.x.
This question is related with the previously linked but differs in the fact that now I will be encapsulating the single property into a Javascript object, so the object that Jackson needs to deserialize won't be a primitive but a POJO.
You won't be able to get individual members as easily, simply because Spring MVC doesn't have any builtin tools to do that. One option is to write your own annotation that describes a parameter at the root of an excepted JSON object body. Then write and register a new HandlerMethodArgumentResolver implementation which processes that annotation on a handler method parameter.
This is not a simple task. Since you can't consume the request content multiple times, you have to save it somehow, in a Filter, for example. For now, let's ignore this restriction and assume we only wanted one parameter. You'd define an annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
#interface JsonObjectProperty {
String name();
}
And the HandlerMethodArgumentResolver
class JsonObjectPropertyResolver implements HandlerMethodArgumentResolver {
/**
* Configured as appropriate for the JSON you expect.
*/
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonObjectProperty.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> parameterType = parameter.getParameterType();
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
MediaType contentType = inputMessage.getHeaders().getContentType();
if (!contentType.equals(MediaType.APPLICATION_JSON_UTF8)) {
throw new HttpMessageNotReadableException(
"Could not read document. Expected Content-Type " + MediaType.APPLICATION_JSON_UTF8 + ", was " + contentType + ".");
}
// handle potential exceptions from this as well
ObjectNode rootObject = objectMapper.readValue(inputMessage.getBody(), ObjectNode.class);
if (parameterType == String.class) {
JsonObjectProperty annotation = parameter.getParameterAnnotation(JsonObjectProperty.class);
return rootObject.get(annotation.name()).asText();
}
// handle more
throw new HttpMessageNotReadableException("Could not read document. Parameter type " + parameterType + " not parseable.");
}
}
and finally the handler method
#RequestMapping(value = "/json-new", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String handleJsonProperty(#JsonObjectProperty(name = "userId") String userId) {
String result = userId;
System.out.println(result);
return result;
}
You'll have to register the JsonObjectPropertyResolver appropriately. Once you do, it will be able to extract that JSON property directly into the parameter.
You can use some JSON inline parsers (similar to XML Xpath) where you can provide your JSON string and ask your parser to retrieve some subdocument as String, List or Map. One of the examples is OGNL. It's quite powerful tool, although it is not the only one and not the most performance efficient, but still mature and stable Apache product. So, in your case you would be able feed your JSON string to OGNL and tell it to retrieve properties "userId" and "role" as separate strings. See the OGNL documentation at Apache OGNL page
I have the following controller. I am using Spring to create Restful APIs.
#RestController
public class UserController extends RestControlValidator {
#RequestMapping(value = "/user/", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List newUser(#RequestBody #Valid UserInput input,BindingResult result)
{Some code}
}
The UserInput class looks like this:
public class UserInput{
#NotEmpty
private String emailId;
#NotEmpty
private String fName;
private String lName;
private int sex;
//getters and setters
Now when I try and access /user/ with data {"sex":"Male"}, I get the following response:
I want the response in case of such a request to be:
{"errors":{"sex":"The value must be an integer"}}
Is there any way of customising BAD REQUEST responses in Spring?
Considering the current scenario the most ideal solution would be to alter the behavior of HandlerMethodArgumentResolve as the json to pojo constructed by #RequestBody fails because we dont get a chance to check the wrong data and this check can very well be done in the custom message converter
A. first we would need to create LanguageMessageConverter as follows
public class LanguageMessageConverter extends
AbstractHttpMessageConverter<Language> {
private Gson gson = new Gson();
public LanguageMessageConverter() {
super(new MediaType("application", "json", Charset.forName("UTF-8")));
}
#Override
protected boolean supports(Class<?> clazz) {
return Language.class.equals(clazz);
}
Map<String, String> mp = new HashMap<>();
#Override
protected Language readInternal(Class<? extends Language> clazz,
HttpInputMessage httpInputMessage) throws IOException,
HttpMessageNotReadableException {
Map langmp = gson.fromJson(
convertStreamToString(httpInputMessage.getBody()), Map.class);
for (Field field : clazz.getDeclaredFields()) {
if (!langmp.get(field.getName()).getClass().getCanonicalName().equals(field.getType().getCanonicalName())) {
if (field.getType().getCanonicalName().equals("java.lang.Integer")||field.getType().getCanonicalName().toString().equals("int")) {
langmp.put(field.getName(), "0");
} else if (field.getType().equals("java.lang.String")) {
//TODO COde needs to be improved here because this check is not efficient
langmp.put(field.getName(), "wrong");
}
}
}
Language lang = gson.fromJson(gson.toJson(langmp), clazz);
return lang;
}
we need to set the media type new MediaType("application", "json", Charset.forName("UTF-8")) which will make sure this class intervenes the mentioned MIME type
Considering we need to manipulate the result I found it best to convert it to map langmp (There are better JSON Parsers which can be used)
Since we need to to understand the existing type I used reflection api to get the fields via getDeclaredFields()
Using the above made the logical check using the datatype to understand if the type is incorrect for eg if the field datatype is int and if it is found as String then corresponding map value will be substituted
once that is done the map will hold the updated values where in if the data was wrong a default value would be set eg if the int var is set to 0 since the originating json had a String in it.
Once that is done the updated map is converted to the concerned class.
B. Secondly we need to register the custom MessageConverter in the dispatcher xml i.e. LanguageMessageConverter
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class="com.comp.org.controller.LanguageMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
register-defaults="true" is very important since we are adding Custom MessageConverter but we also need the other existing converters working along with the one we have added
LanguageMessageConverter needs to be registered here.
C. Considering the concerned pojo is populated with the necessary details it would reach our controller post processing in the custom converter now we would add the manual validation eg. if the int variable has 0 the necessary error json should be returned
As per your request even if the json consists of the wrong data the custom message converter should process it and accordingly in the controller we can validate the condition mentioned.
The code definitely can be improved further. Kindly let me know if this solution fulfilled your requirement or any part of the code requires further elaboration and hopefully addressed your concern.
I had the same issue, than I solved that way:
Create an Object called Error, like that (don't forget to implement Serializable...):
private String fieldName;
private String errorCode;
private String defaultMessage;
public Error() {
}
public Error(String fieldName, String errorCode, String defaultMessage) {
this.fieldName = fieldName;
this.errorCode = errorCode;
this.defaultMessage = defaultMessage;
}
/* getters, setters */
Inside the #RestController method you ave to call inputValidator.validate() method (if you didn't create an Object Validator for your UserInput then we're really don't speaking the same language...)
// validating the userInput
userInputValidator.validate(userInput, bindingResult);
if (bindingResult.hasErrors()) {
List<Error> errors = new ArrayList<>(bindingResult.getErrorCount());
for (FieldError fieldWithError : bindingResult.getFieldErrors()) {
errors.add(new Error(fieldWithError.getField(), fieldWithError.getCode(), fieldWithError.getDefaultMessage()));
}
return errors;
}
// in case of success:
return null;
Finally you'll have to translate the JSON object to your client side. You'll have two kind of objects:
3.1. null (undefined depending on the language you're using)
3.2. A JSON object like that:
[
{
"fieldName": "name",
"errorCode": "user.input.name.in.blank",
"defaultMessage": "Insert a valid name!"
},
{
"fieldName": "firstPhone",
"errorCode": "user.input.first.phone.blank",
"defaultMessage": "Insert a valid first phone!"
}
]