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
}
Related
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"
}
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());
In my organisation, when I want to expose an API, I have to declare it with a swagger contract, same for any update, and it can take multiple weeks before the creation or change is taken into account.
That's why we've come with the idea to declare only one contract for all the APIs we need to expose, and manage the routing in an applicative reverse proxy (the request would include the necessary metadata to allow to route to the appropriate endpoint) :
{
"genericHttpRequest" : base64encodedByteArrayOfAnyHttpRequest
}
Now the question is :
how to manage this request without reimplementing HTTP ? Is it possible to put back the array of byte into a structured HttpServletRequest ?
/**
* Manage a generic request
*/
#RequestMapping(value = "/genericRequest", method = RequestMethod.POST)
public #ResponseBody void manageGenericRequest(#RequestBody GenericHttpRequestDto body) {
byte[] genericHttpRequest = body.getGenericHttpRequest();
//(...)
}
Spring will inject a HttpServletRequest if it is set as a method parameter. Furthermore, wildcard path mappings will enable the methods to be matched to every request:
#RestController
#RequestMapping("/generic-endpoint/**")
public class DemoController {
#RequestMapping
public ResponseEntity<Object> genericGetRequest(HttpServletRequest httpServletRequest) {
return ResponseEntity.ok().body(httpServletRequest.getMethod());
}
}
Optionally, you could return a ResponseEntity to gain more control over your HTTP response.
I have looked at the various answers and they do not resolve my issue. I have a very specific client need where I cannot use the body of the request.
I have checked these posts:
Trying to use Spring Boot REST to Read JSON String from POST
Parsing JSON in Spring MVC using Jackson JSON
Pass JSON Object in Rest web method
Note: I do encode the URI.
I get various errors but illegal HTML character is one. The requirement is quite simple:
Write a REST service which accepts the following request
GET /blah/bar?object=object11&object=object2&...
object is a POJO that will come in the following JSON format
{
"foo": bar,
"alpha": {
"century": a,
}
}
Obviously I will be reading in a list of object...
My code which is extremely simplified... as below.
#RequestMapping(method=RequestMethod.GET, path = "/test")
public Greeting test(#RequestParam(value = "object", defaultValue = "World") FakePOJO aFilter) {
return new Greeting(counter.incrementAndGet(), aFilter.toString());
}
I have also tried to encapsulate it as a String and convert later which doesnt work either.
Any suggestions? This should really be extremely simple and the hello world spring rest tut should be a good dummy test framework.
---- EDIT ----
I have figured out that there is an underlying with how jackson is parsing the json. I have resolved it but will be a write up.. I will provide the exact details after Monday. Short version. To make it work for both single filter and multiple filters capture it as a string and use a json slurper
If you use #RequestParam annotation to a Map<String, String> or MultiValueMap<String, String> argument, the map will be populated with all request parameters you specified in the URL.
#GetMapping("/blah/bar")
public Greeting test(#RequestParam Map<String, String> searchParameters) {
...
}
check the documentation for a more in depth explanation.
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