Spring boot #RequestBody default POJO mapping behavior? - java

I have a java class with uppercase field names and some of them with under scroll, like this:
public class DATADto {
private String UPPERCASE;
private String UNDER_SCROLL;
public String getUPPERCASE() { return UPPERCASE; }
public void setUPPERCASE(String s) { UPPERCASE = s; }
...//setters and getters
}
and I used this in a rest endpoint that accepts json in a spring rest controller:
#RestController
#RequestMapping({"/api/path"})
public class MyRestController {
#PostMapping(path = {"/Data"}, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> useDATADto(#RequestBody DATADto aDATADto ) {
//do something
}
}
what JSON fields do I need to send by default and why?

The story goes like this..
Spring Boot by default uses Jackson ObjectMapper to serialize and deserialize Java objects.
In this context, by serialization we mean the conversion of java objects into json, deserialization is the reverse process.
Regarding the #RequestBody annotation, the following is written in the documentation:
Annotation indicating a method parameter should be bound to the body
of the web request. The body of the request is passed through an
HttpMessageConverter to resolve the method argument depending on the
content type of the request. Optionally, automatic validation can be
applied by annotating the argument with #Valid.
In short, #RequestBody annotation tells Spring to deserialize an incoming request body into an object passed as a parameter to the handler method. Spring achieves this using MessageConverter
Since Spring Boot uses Jackson by default for serializing and deserializing request and response objects in your REST APIs, and Jackson uses MappingJackson2HttpMessageConverter, so that will be message converter implementation that spring will use. You can read more about that here.
The important thing is that Jackson uses Java Bean naming conventions to figure out the json properties in a Java class. Acutally it uses default PropertyNamingStrategy . Here is what is written in documentation:
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
So, since you didn't set any naming strategy, it will use default one.
Beacause of that, if you send payload like this :
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}
That won't work, you will get exception, since there jackson won't find under_scroll property in your class, it will look for under_SCROLL , therefore this payload:
{
"uppercase": "YOUR_VALUE",
"under_SCROLL": "YOUR_VALUE"
}
will work.
To change default PropertyNamingStrategy check
this article.

It will depend on the Jackson property naming strategy. The default is LOWER_CAMEL_CASE , so your request body should look like this:
{
"uppercase": "test",
"under_scroll": "test"
}
For all possible configurations of the naming strategy for Jackson please refer to the document «Class PropertyNamingStrategy»
If you're using Spring, you may use this property to configure the naming strategy:
spring.jackson.property-naming-strategy
Another possible way will be the bean configuration:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder jacksonMapper = new Jackson2ObjectMapperBuilder();
jacksonMapper.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
return jacksonMapper;
}
Additional note:
Your current naming approach doesn't follow the Java Code Conventions. If you need to process JSON with some specific naming format better to use the #JsonProperty annotation on the fields of your POJO.
Please see the example below:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
#Data
public class DATADto {
#JsonProperty("UPPERCASE")
private String uppercase;
#JsonProperty("UNDER_SCROLL")
private String underScroll;
}

You should send post request to /api/path/data with this request body:
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}

Related

How to properly configure jackson converters for JSON and plain text

For a Spring MVC (not Spring Boot) I've had to change the configuration class that extended WebMvcConfigurationSupport to implement WebMvcConfigurer and add the #EnableWebMvc annotation. This causes problems with the conversion of the responses for several endpoints. The project defaults to application/json and it is used for most of the responses however, there are several endpoints which return application/xml and even a few that return text/plain. JSON responses are modified to remove fields containing null using the following Java config:
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
This causes JSON responses to be returned correctly but results in an exception for the text/plain endpoints. They then produce an error:
No converter for [class java.lang.String] with preset Content-Type 'null'
The error can be resolved by adding the default string converter before the JSON converter:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(jsonConverter());
}
However, this causes a problem specifically for endpoints that return JSON but in Java only have String as the return type. A string in between double quotes should be returned: "response", but they only return the string without quotes: response. This makes most clients to not recognise the response as valid JSON. Curiously POJOs are still converted to valid JSON.
How can I configure my Spring MVC (not Spring Boot) project using a configuration class that implements WebMvcConfigurer and is annotated with #EnableWebMvc to return JSON without null fields and single strings as valid JSON (e.g. with double quotes: "response") but also plain text?
A suitable solution has been found to have the REST API return valid JSON responses even for methods which return a string (so with double quotes around the returned string) while also being able to return XML and plain text responses.
In the configuration class that implements WebMvcConfigurer and is annotated with #EnableWebMvc we register the default converters by overriding the extendMessageConverters. The default MappingJackson2HttpMessageConverter is removed from the list and our custom JSON converter is added instead as the first converter in the list. By adding the JSON converter before the StringHttpMessageConverter methods that return a string have their responses converted to valid JSON (with double quotes) while application/xml and text/plain responses also work properly.
What I can suggest is add one more bean for handling string responses as you added for json and add the convertor to converters.add(stringConverter());

How do I encode Feign HTTP GET request params using Jackson?

I'm quite new to Feign. My aim is to use the Jackson Encoder/Decoders via HTTP to communicate between clients. To achieve this I used the following configuration:
#Configuration
protected static class JacksonFeignConfiguration {
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
#Bean
public Encoder feignEncoder() {
return new JacksonEncoder();
}
}
While this appears to format the body of requests, it does not format request parameters (using #RequestParam). These are unexpectedly created using the toString() method which is not well formatted.
How do I ensure that request parameters are also formatted using Jackson. This is key as I need to include a list of filter criteria objects within GET requests.
Currently, I have worked around this by changing the filter criteria object toString() method to return a JSON string and writing a matching argument resolver that can decode that string.
Is this the only way or can it be automated via configuration?
Need to set this configuration class in FeignClient interface.
#FeignClient(value = "client", configuration = JacksonFeignConfiguration.class)
public interface YourClient{
//Your mappings
}

Ambiguous mapping when using RequestBody annotation

I am trying to create an endpoint in Spring Boot that accepts both single object or an array of these objects. I know that mappings need to have unique signiture so I am wondering what is the correct way to make it work using POJOs?
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postSingleFoo(HttpServletRequest request,
#RequestBody(required = true) Foo foo) {
// process
}
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
#RequestBody(required = true) Foo[] foo) {
// process
}
Obviously I am getting an exception for ambiguous mapping. But I would still like to use POJOs in my #RequestBody annotation since i am performing couple of conversions in them.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'fooController' method
public void com.usquared.icecream.lrs.controller.FooController.postSingleFoo(javax.servlet.http.HttpServletRequest,java.lang.String)
to {[/foo],methods=[POST]}: There is already 'fooController' bean method
What is the recommended approach to implement such feature correctly?
This is not a problem that can be fixed through Spring MVC. Spring MVC creates mappings from the #RequestMapping annotating your handler methods. These help distinguish how Spring MVC delegates HTTP requests to be handled by your methods. Your current configuration attempts to map two handler methods to the same request details. That can never work.
One solution, assuming you're expecting JSON and working with Jackson, is to configure your ObjectMapper to accept single values as arrays and define a single handler method with an array parameter. For example, you'd keep only this handler method
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
#RequestBody(required = true) Foo[] foo) {
// process
}
but configure your ObjectMapper as such
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
The configuration depends on how your application is configured. With Spring Boot, it should be as simple as declaring a #Bean method for ObjectMapper. With your typical Spring MVC application, you'll need to register a MappingJackson2HttpMessageConverter with a custom ObjectMapper.
If your JSON, the request body, contained
{
"someProperty":"whatever"
}
Jackson would be able to wrap the single value into a Foo[] and Spring MVC would pass that as an argument to your handler method. You can then check the length of the array and act accordingly.

How to convert a JSON to java object and vice versa in spring controller?

I want to create a simple Spring project that will serve as a RESTful service.
I want to send JSON from frontend and want to convert it to a Java object using #RequestBody. After modifying the object in the backend, I need to convert that object back to JSON and send to front end.
How can I achieve this?
You can use the Jackson library. An example can be found here: http://www.mkyong.com/spring-mvc/spring-3-mvc-and-json-example/
Serialization (POJO -> JSON) and deserialization (JSON -> POJO) in Spring is simply obtained via #RequestBody and #ResponseBody annotations.
You just need to define a Java class that represents/maps your JSON object on server-side.
Example:
Input JSON
{id: 123, name: "your name", description: ""}
Java class
public class MyClass {
private int id;
private String name;
private String description;
}
Methods in your controller
public void postJson(#RequestBody MyClass o){
// do something...
}
public #ResponseBody MyClass getJson(){
// do something...
}
NOTE I omitted #RequestMapping settings.
You will have to provide csrf token for POST request. Instead you can try this.
sending HashMap by angularjs $http.get in spring mvc
It works fine just a bit extra #RequestParams but on the better side you can send additional information too and not only the respective object.

Automatic conversion of JSON form parameter in Spring MVC 4.0

I am trying to build a Spring MVC controller which will receive a POSTed form with a parameter in JSON format, and have Spring automatically convert it to a Java object.
Request content type is application/x-www-form-urlencoded
The name of the parameter that contains a JSON string is data.json
This is the controller:
#Controller
public class MyController {
#RequestMapping(value = "/formHandler", method = RequestMethod.POST)
public #ResponseBody String handleSubscription(
#RequestParam("data.json") MyMessage msg) {
logger.debug("id: " + msg.getId());
return "OK";
}
}
And this is what the MyMessage object looks like:
public class MyMessage {
private String id;
// Getter/setter omitted for brevity
}
Perhaps not surprisingly, posting a form with parameter data.json={"id":"Hello"} results in HTTP error 500 with this exception:
org.springframework.beans.ConversionNotSupportedException:
Failed to convert value of type 'java.lang.String' to required type 'MyMessage'
nested exception is java.lang.IllegalStateException:
Cannot convert value of type [java.lang.String] to required type [MyMessage]: no matching editors or conversion strategy found
If I read the MappingJackson2HttpMessageConverter docs correctly, Jackson JSON conversion is triggered by Content-Type application/json, which I obviously cannot use since this is a form POST (and I don't control the POSTing part).
Is it possible to get Spring to convert the JSON string into an instance of MyMessage, or should I just give up, read it as a String and perform the conversion myself?
Spring invokes your #RequestMapping methods with reflection. To resolve each argument it's going to pass to the invocation, it uses implementations of HandlerMethodArgumentResolver. For #RequestParam annotated parameters, it uses RequestParamMethodArgumentResolver. This implementation binds a request parameter to a single object, typically a String or some Number type.
However, your use case is a little more rare. You rarely receive json as a request parameter, which is why I think you should re-think your design, but if you have no other choice, you need to register a custom PropertyEditor that will take care of converting the request parameter's json value into your custom type.
Registration is simple in an #InitBinder annotated method in your #Controller class
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(MyMessage.class, new PropertyEditorSupport() {
Object value;
#Override
public Object getValue() {
return value;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
value = new Gson().fromJson((String) text, MyMessage.class);
}
});
}
In this particular case, we don't need all the methods of the PropertyEditor interface, so we can use PropertyEditorSupport which is a helpful default implementation of PropertyEditor. We simply implement the two methods we care about using whichever flavor of JSON parser we want. I used Gson because it was available.
When Spring sees that it has a request parameter that you requested, it will check the parameter type, find the type MyMessage and look for a registered PropertyEditor for that type. It will find it because we registered it and it it will then use it to convert the value.
You might need to implement other methods of PropertyEditor depending on what you do next.
My recommendation is to never send JSON as a request parameter. Set your request content type to application/json and send the json as the body of the request. Then use #RequestBody to parse it.
You can also use #RequestPart like this:
#RequestMapping(value = "/issues", method = RequestMethod.POST, headers = "Content-Type=multipart/form-data")
public String uploadIssue(#RequestParam("image") MultipartFile file, #RequestPart("issue") MyMessage issue)

Categories

Resources