I have a Spring Boot application (2.4.1), where an OffsetDateTime field is returned as float value from RestController. Example:
"created_at": 1616080724.531610100
I tried all the suggested solutions in this thread. None of them worked for me.
I also tried to add a very simple end-point that only returns OffsetDateTime:
#GetMapping("/test")
public OffsetDateTime test() {
return OffsetDateTime.now();
}
The result is the same, it's returned as float value.
Then I tried the same end-point in a minimal Spring Boot project and it's returned in ISO format as expected:
"2021-03-18T15:39:14.5295632+01:00"
This all points to some transitive dependency messing up with the default Jackson serializers used by Sprint Boot. But mvn dependency:tree doesn't give me any suspicious dependencies (e.g. no gson marshaller dependency).
I also tried enabling TRACE logging, and I can see that the object written in HttpEntityMethodProcessor has the correctly formatted created_at time:
TRACE org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Writing [class MyObject {
....
createdAt: 2021-03-18T16:37:34.113316500+01:00
...
But it still ends up as float on the client side (testing on browser and with Postman). What could be the problem here?
After some debugging in Jackson classes, I found out that InstantSerializerBase#serialize method was being called with the default SerializerProvider (DefaultSerializerProviderImpl), which had the SerializationFeature.WRITE_DATES_AS_TIMESTAMPS feature enabled. That resulted in serializing OffsetDateTime values as epoch seconds + nanos.
I was able to fix the problem by adapting our WebMvcConfigurer implementation as follows:
#Configuration
#EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
// Some other configuration
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}
private ObjectMapper objectMapper() {
return new ObjectMapper()
.disable(WRITE_DATES_AS_TIMESTAMPS)
.registerModule(new JavaTimeModule());
}
}
After this change, OffsetDateTime fields are finally serialized in ISO format; e.g.
"created_at": "2021-03-19T17:05:27.785646+01:00"
Soution with configureMessageConverters is exacty what I needed. I have the same problem and you really helped me. Thanks!
Maybe you should report the solution to Spring
Related
So I had a perfectly working Spring app. Most of my controller methods are for ajax calls that return JSON via #ResponseBody with the jackson api and returns my Java POJO to JSON.
I have a need to turn XML to JSON, so I find that Jackson has a tool for that, and I add this to my POM to use the library:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
So that I may use this:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(sb.toString().getBytes());
But now the #ResponseBody is returning XML and not JSON. I Remove the dependency and the controllers return JSON again.
Any way to get both? I want the xmlMapper, and JSON from the response body.
jackson-dataformat-xml appears to be registering a MappingJackson2HttpMessageConverter with a XmlMapper, along with other HttpMessageConverters that work with XML. If you always intended to return JSON from your controllers, you can change what HttpMessageConverter your app uses by overriding configureMessageConverters
For Spring 5.0 and above,
#Configuration
public class HttpResponseConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> supportsXml(converter) || hasXmlMapper(converter));
}
private boolean supportsXml(HttpMessageConverter<?> converter) {
return converter.getSupportedMediaTypes().stream()
.map(MimeType::getSubtype)
.anyMatch(subType -> subType.equalsIgnoreCase("xml"));
}
private boolean hasXmlMapper(HttpMessageConverter<?> converter) {
return converter instanceof MappingJackson2HttpMessageConverter
&& ((MappingJackson2HttpMessageConverter)converter).getObjectMapper().getClass().equals(XmlMapper.class);
}
}
For older versions of Spring, replace implements WebMvcConfigurer with extends WebMvcConfigurerAdapter
Add Accept: application/json to HTTP request header.
Read this for an analysis of how Spring does content negotiation and allows producing either XML or JSON.
The simplest way is to add an extension at the URL: Instead of /path/resource use /path/resource.json
You may also add a format parameter e.g. /path/resource?format=json or pass an appropriate Accept header
In my case, the XmlMapper was actually inserted into the application context as an #Bean. The other solutions here did not work for me. It seems like one of those issues where context matters, so for people coming here from a different context than the other answerers, here's another angle: I had to insert my own ObjectMapper.
#Configuration
public class XmlMapperConfiguration {
#Bean // me, culprit
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean // to make sure the rest of the application still works with JSON
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
I'm going to throw in an #Primary on the ObjectMapper one. It seems suspicious that Spring would even choose that one consistently. Since XmlMapper extends ObjectMapper, why would it not take that one, so #Primary won't hurt.
I have the following resource that consumes a JSON being mapped to a POJO.
#Path("example")
public class ExampleResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response addThesis(MyObject myObject) {
return Response.ok().entity("Test").build();
}
}
Here's the POJO class:
public class MyObject {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
When I send a POST request with the body {"title":"Test title"} everything works fine. The response is Test, as expected. However, when I change the request to {"titlee":"Test title"} the server replies with this:
Unrecognized field "titlee" (class com.my.package.MyObject), not marked as ignorable (one known property: "title"])
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#6dc6a46a; line: 2, column: 11] (through reference chain: com.my.package.MyObject["titlee"])
Obviously this is an exception thrown and returned by Jersey. How can I intercept this exception and return a custom status code and message?
What I've tried so far is to implement my own ExceptionMapper:
#Provider
public class MyJsonExceptionMapper implements ExceptionMapper<JsonProcessingException> {
public Response toResponse(JsonProcessingException e) {
return Response.status(400).entity("JSON Processing Error").build();
}
}
Unfortunately the response stays the same. When I implement an ExceptionMapper for a custom exception and throw the corresponding exception in the resource method though, everything works fine. I assume this has to do with the default ExceptionMapper for JsonProcessingException overriding my own one. Then I tried to create a generic mapper ("implements ExceptionMapper"), but again no success.
I've looked literally everywhere and tried many things including extending ResourceConfig and registering my mapper, but nothing has worked so far.
Some more information that might help to narrow the problem down: I am using Grizzly2 as the HTTP server which I am deploying as a Fat JAR.
The dependency part of my pom.xml looks like this:
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.24</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>2.24</version>
</dependency>
</dependencies>
Any advice is highly appreciated.
Ok, this is dumb and hack-ish, but worked for me:
register(JacksonJaxbJsonProvider.class);
This is due to the following "nice default behavior" in the Jackson feature entry point:
if (!config.isRegistered(JacksonJaxbJsonProvider.class)) {
// add the default Jackson exception mappers
context.register(JsonParseExceptionMapper.class);
context.register(JsonMappingExceptionMapper.class);
:(
But, I'd still prefer an answer that fixes the problem "for real" - ie. without pre-registering components so that the feature cannot configure them properly...
I also faced this issue. If JacksonFeature is registered, you can simply register JacksonJaxbJsonProvider as a workaround.
When the JacksonFeature is in the classpath, it is automatically discovered by Jersey. Another approach to fix it is disabling auto discovery by setting ServerProperties.FEATURE_AUTO_DISCOVERY_DISABLE to true. As a result of this, you would need to register other features manually.
Alternatively you can get rid of the jersey-media-json-jackson artifact and use jackson-jaxrs-json-provider instead. With this, you will get rid of JacksonFeature and then you can register your own exception mappers.
One last option and probably what seems to be the correct solution (as pointed in Kysil Ivan's answer) you can write your own exception mapper and then give it a high priority, such as 1. If you use auto discovery, just annotate it with #Provider and #Priority:
#Provider
#Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
...
}
If you manually register your provider, you can give your provider a binding priority:
#ApplicationPath("/")
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(JsonParseExceptionMapper.class, 1);
}
}
See this answer for more details.
We use JAX-RS on Wildfly to implement our web services and use the following to accomplish what you are trying to do with Jersey on Glassfish. Maybe it has similar features which you could look up. Our steps are:
The service is a stateless EJB, use EJB interceptor to trap exception
and populate request scoped object with details
Implement a PostProcessInterceptor which reads from request scoped object and modifies response before service returns. (This is specific to JAX-RS)
We are migrating from Jersey 1 to Jersey 2.
Up until now we were using ContextResolver configured like this:
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONConfiguration.MappedBuilder;
#Provider
#Produces("application/json")
public class JSONJAXBContextResolver implements ContextResolver<Class<?>> {
#Override
public JAXBContext getContext(Class<?> objectType) {
MappedBuilder mapped = JSONConfiguration.mapped();
mapped.arrays("Property"); //$NON-NLS-1$
mapped.arrays("option"); //$NON-NLS-1$
JSONConfiguration build = mapped.xml2JsonNs(NamespacesMapper.getNamespacesMap()).build();
return new JSONJAXBContext(build, objectType);
}
}
All is good, the produced json looked like this(root xml element is unwrapped i.e. removed):
{"#id":"as213","code":"ERR12","cause":{"validationMessages":{"validationMessage":{"message":"some message","details":"some details","severity":"ERROR"}}}}
However, with jersey 2 there is no more JSONConfiguration.mapped().
Instead we are looking into jettison way of doing the same thing.
So we now have:
#Provider
#Produces("application/json")
public class JSONJAXBContextResolver implements ContextResolver<Class<?>> {
#Override
public JAXBContext getContext(Class<?> objectType) {
MappedJettisonBuilder mappedJettison = JettisonConfig.mappedJettison();
mappedJettison.serializeAsArray("Property"); //$NON-NLS-1$
mappedJettison.serializeAsArray("option"); //$NON-NLS-1$
JettisonConfig build = mappedJettison.xml2JsonNs(NamespacesMapper.getNamespacesMap()).build();
return new JettisonJaxbContext(build, objectType);
}
However this produces the following:
{"error":{"#id":"as213","code":"ERR12","cause":{"validationMessages":{"validationMessage":{"message":"some message","details":"some details","severity":"ERROR"}}}}}
Notice the "root" element "error". This breaks our JSON representation big time.
I've spent almost 2 days now trying to figure out how to configure Jettison to exclude the xml root element, but to no avail.
I've noticed the following in the JettisonConfig javadoc:
https://jersey.java.net/apidocs/2.1/jersey/org/glassfish/jersey/jettison/JettisonConfig.html#DEFAULT
public static final JettisonConfig DEFAULT
The default JettisonConfig uses JettisonConfig.Notation.MAPPED_JETTISON notation with root unwrapping option set to true.
However even using DEFAULT configuration instead of Mapped does not produce the desired json - the root "error" element is still there.
I've even looked in the Jettison source for configuration property controlling this behavior but could not find anything.
Does anyone know how and if it is possible to make Jettison ignore the root XML element?
I'm using Jackson 1.9.6 (codehaus) for JSON serialization of my response bodies in a Spring MVC application, and I'm having trouble finding a way to configure pretty printing. All of the code examples I've been able to find (like this and this) involve playing with an instantiation of ObjectMapper or ObjectWriter, but I don't currently use an instantiation of these for anything else. I wouldn't even know where to put this code. All of my Jackson configurations are taken care of by annotating the POJOs being serialized to JSON.
Is there a way to specify pretty printing in an annotation? I would think they would have put that in #JsonSerialize, but it doesn't look like it.
My class to be serialized looks like this:
#JsonAutoDetect
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class JSONObject implements Serializable{...}
and my Spring controller method looks like this:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<Object> getMessagesAndUpdates(HttpServletRequest request, HttpServletResponse response) {
JSONObject jsonResponse = new JSONObject();
.
.
.
//this will generate a non-pretty-printed json response. I want it to be pretty-printed.
return jsonResponse;
}
I searched and searched for something similar and the closest I could find was adding this bean to my Application context configuration (NOTE: I am using Spring Boot so I am not 100% certain this will work as-is in a non-Spring Boot app):
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder()
{
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
return builder;
}
In my opinion, its the cleanest available solution and works pretty well.
Adding this as a separate answer so I can format the output.
As luck would have it, the non-Spring Boot solution wasn't too far from the Spring Boot solution :)
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
When we configure Spring AOP the JSON Results disappear for : AOPExression1
<aop:pointcut id="dmhMethodExecution"
expression="within(com.aditya.dmh..*)" />
So I added an exclusion for : AOPExpression1 as AOpExpression2
<aop:pointcut id="dmhMethodExecution"
expression="within(com.aditya.dmh..*)
and !within(com.aditya.dmh.controller..*)" />
in the ASPECTJ Expression
Still I donot see my JSON results from the controller which is a restful implementation.
package com.aditya.dmh.controller;
#Controller
public class EmployeeController {
private EmployeeServiceInterface employeeService;
#Autowired
public void setEmployeeService(EmployeeServiceInterface employeeService) {
this.employeeService = employeeService;
}
#RequestMapping("/employeeservices/1/allemployees.view")
public #ResponseBody Result<EmployeeModel> getEmployees(){
return employeeService.getEmployees(0, 10);
}
}
When I use log4j for the DEBUG messages I see the following:
15:37:04.214 [http-8090-1] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dmhServiceDispatcher': assuming HandlerAdapter completed request handling
15:37:04.214 [http-8090-1] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
When I remove the AOP the JSON results start to appear and I see that the additional Debug Message.
17:11:36.270 [http-8090-2] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Written [com.aditya.Result#8a85268] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#62ba2e48]
Looking at the Spring forums I understand that the Convertor is automatically configured when the
<mvc:annotation-driven/>
is used.
Is my problem of configuring AOP have anything to do with the RequestResponseBodymethodProcessor not being called.
Does this have anything to do with the proxies created around my controller when I use AOPExpression1. Why would an exclusion as in AOPExpression2 still have the problem.
Anyhelp would be appreciated
I belive that to intercept a request to a controller you should do it with MVC interceptors and not with aspects. What I did is to put into the applicationContext.xml this:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/employeeservices/1/allemployees.view"/>
<bean class="com.aditya.dmh.interceptor.ResultInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
Now, the class ResultInterceptor is where you put the code you want to be done, for instance:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("--- preHandle --- ");
return true;
}
At least this is the way I did it.
Hope it helps.
This is a bit of a speculation:
I think what is happening is a CGLIB based dynamic proxy is getting created for your controller (although you have excluded it explicitly in your new pointcut expression), if this happens then #RequestMapping annotations are not correctly detected(by `) and so the controller is not there to handle your REST request.
Can you try a few things:
Have an interface for the controller with the exact same methods that the controller handles, and put the #RequestMapping annotations there, this will handle cases where the dynamic proxy is created and should work as expected even if the dynamic proxy gets created..
Play around a little more with your pointcut expression to see why a proxy for you controller may be getting created.
THE SOLUTION FOR OUR PROBLEM IN THIS CONTEXT
We found out that the whole thing was with the Around Advice in AOP Configuration that we have had.
Before Fix
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.debug(buildLogMessage(new StringBuilder().append(METHOD_AROUND_ID)
.append("[").append(totalTime).append("] ").toString(),
joinPoint));
return returnValue;
}
After Fix
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object returnValue = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.debug(buildLogMessage(new StringBuilder().append(METHOD_AROUND_ID)
.append("[").append(totalTime).append("] ").toString(),
joinPoint));
return returnValue;
}
the void effectively made sure that the Response Object sent by the logAround was not passed on back to the RequestResponseBodyMethodProcessor
Once we had it captured & returned the cglib proxies sent the response back to the processor & had the response sent back to the client.