How to properly configure jackson converters for JSON and plain text - java

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

Related

Spring boot #RequestBody default POJO mapping behavior?

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"
}

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
}

How should I configure httpMessageConverters in Spring boot to convert messages into required format?

I have written an api service using Spring Boot, which serves the requests in Google ProtoBuf format. My requirement is whenever a user hits my service with accept header as application-json format I want to convert the rendered proto objects into JSON format using proto-buf-java Format (library for converting JSON strings to and from proto Objects). Whats happening right now is whenever user hits for application/x-protobuf format, spring uses protobufHTTPMessageConverter to convert into required format, but when the user hits for Json format spring boot uses default JacksonMapper and it couldn't be able to convert into JsonString. I am new to Spring Boot and I need solution to configure message converters to serve JSON format too.
Try to add the class below. SpringBoot should scan for it assuming you left AutoScan on. On the request for your API it will return the object serialized to JSON by default or just add &mediaType=json to request.
#EnableWebMvc
#Configuration
public class CustomConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setPrettyPrint(true);
mappingJackson2HttpMessageConverter.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
converters.add(mappingJackson2HttpMessageConverter);
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.defaultContentType(MediaType.APPLICATION_JSON);
configurer.favorPathExtension(false);
configurer.favorParameter(true);
configurer.ignoreAcceptHeader(true);
configurer.useJaf(false);
configurer.parameterName("mediaType");
configurer.mediaType("json", MediaType.APPLICATION_JSON);
}
}

Swagger 2 accept xml instead of json

I have a project with spring boot and I want to use swagger2 to document my json web services.
I have this configuration :
#Configuration
#EnableSwagger2
public class Swagger2Config {
#Bean
public Docket welcomeMessageApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My API")
.description("Lorem Ipsum is simply dummy text of ...")
.termsOfServiceUrl("an url")
.contact("contact")
.license("")
.licenseUrl("")
.version("2.0")
.build();
}
To read the documentation, I use this link : http://localhost:9081/v2/api-docs
In the swagger UI, it works fine. But when I try this link directly in my browser, I have this error :
With Firebug, I see that it accept XML content instead of JSON content.
How can I modify swagger configuration to accept JSON content ?
You meet the problem because of the Spring MVC default get the server to render XML instead of JSON in a browser.
The official document say:
To get the server to render XML instead of JSON you might have to send an Accept: text/xml header (or use a browser).
So all you need to do is make the server render JSON in browser.
When you deep into the request in browser you'll see the Request Header:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
And if you debug into the spring boot, you will see the spring mvc will default delegate HttpMessageConverters include MappingJackson2XmlHttpMessageConverter and MappingJackson2HttpMessageConverter.
The MappingJackson2HttpMessageConverter is to render json and MappingJackson2XmlHttpMessageConverter is to render xml.
They both have a field supportedMediaTypes which means what mediatypes are supported.
The value of supportedMediaTypes in MappingJackson2HttpMessageConverter is:
The value of supportedMediaTypes in MappingJackson2XmlHttpMessageConverter is:
There is a 'text/xml;charset=UTF-8' in MappingJackson2XmlHttpMessageConverter.This is why browser render xml instend of json.
So you need add a custom MappingJackson2XmlHttpMessageConverter which support 'text/xml', for example :
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> list = new ArrayList<>();
list.add(MediaType.APPLICATION_JSON_UTF8);
list.add(new MediaType("text", "html", Charset.forName("UTF-8")));
list.add(new MediaType("application", "*+json", Charset.forName("UTF-8")));
converter.setSupportedMediaTypes(list);
converters.add(converter);
}
}
Try this and browser will render JSON instead of XML in browser, and all things right!
What worked for me was updating the Maven dependencies for springfox-swagger2 and springfox-swagger-ui.
The newest working combination for me was:
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
I had a similar issue that the Swagger API returns XML when I tried to read it with a web-browser.
I resolved it with a different approach.
If you try calling the endpoint with some other tools, such as PostMan or Curl command tool, it would be return JSON correctly.
It looks like it happened in a web-browser only.
If you have Jackson XML extension in pom.xml, you may try this approach.
But if you need to return XML data as a response, please find out another solution.
This is my approach, add below Configuration which extends the WebMvcConfigurationSupport class to the project source code.
#Configuration
public class MessageConverterConfiguration extends WebMvcConfigurationSupport {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
HttpMessageConverter<?> target = null;
for (HttpMessageConverter<?> converter : converters) {
if (converter.getClass() == MappingJackson2XmlHttpMessageConverter.class) {
target = converter;
}
}
if (target != null) {
converters.remove(target);
}
}
}
For regular GET type request, web-browser sends a request data to the endpoint, including the 'Accept' key in request header, having 'xml' value in it.
Besides, on the server-side, even if the response data type is JSON, and even if there's JSON message converter specified, Spring picks up the XML message converter first, if it is compatible with the JSON data.
On top of the above, the selection process is based on the order of 'Accept' value from request.
You may debug the AbstractMessageConverterMethodProcessor, especially the writeWithMessageConverters method during the response
process.
In the selecting process, Spring picks up a proper converter based on the registered message converter list which is initialized on the process of the system initialization by the WebMvcConfigurationSupport.
The WebMvcConfigurationSupport provides extendMessageConverters to extend or modify the converter list.
So I tried to remove that special XML message converter from the list and it works.
Although above solution worked for me, you may not use this approach.
Please find out another solution if you need to return XML data as a response.

Spring MVC : Passing XML as POST parameter truncates the XML when equals character is encountered

In my current application i am required to pass two parameters to the REStful Spring MVC application. One parameter is an id and the other is an xml passed as a string.
templateXml=<?xml version="1.0" encoding="UTF-8"?><template name="my_document">
.
.-- other elements of xml--
<template/>
&typeid=TYP001
There is a complex operation that needs to be performed before the spring container passes the parameter to the controller method. Therefore i am using a Custom HttpMessageConverter to parse the obtained XML into a Template object.
Here is the part of the Custom Converter that i am using
public class TemplateConverter extends AbstractHttpMessageConverter<Template>{
#Autowired
private TemplateBuilder templateBuilder;
#Autowired
private MappingJackson2HttpMessageConverter jacksonMessageConverter;
public TemplateConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
supportedMediaTypes.add(MediaType.TEXT_HTML);
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_XML);
super.setSupportedMediaTypes(supportedMediaTypes);
}
#Override
protected Template readInternal(Class<? extends Template> type, HttpInputMessage input) throws IOException,
HttpMessageNotReadableException {
Map<String,String> paramMap = getPostParameter(input);
String documentTypeId = paramMap.get("typeid");
//HERE COMES THE PROBLEM. WHAT I RECIEVE is only <?xml version
String templateXml = paramMap.get("templateXml");
Template template = null;
try {
template = templateBuilder.build(templateXml);
if(documentTypeId!=null){
template.setDocumentTypeId(documentTypeId);
}
To Test this i am using FireFox Rest Client as follows
The problem is that i am just recieving <?xml version from http request where as the typeid parameter is perfectly mapped.
All suggestions are welcome.
Resolved by encoding the xml POST parameter using apache commons codec. So first i encoded the xml then added the encoded string as a paramter to the RESTClient. And decoded again using mentioned apache utility in the converter.
If someone has a better way please let me know

Categories

Resources