Changing Default JSON Time Format with RESTEasy 3.x - java

I am using RESTEasy to implement a REST Service using JSON serialization. Currently, Dates are getting serialized to milliseconds since 1970. To improve compatibility, I would like to get my dates into one of two formats; milliseconds + timezone offset or ISO 8061.
It seems that RESTEasy used to use Jettison for JSON serialization, but from what I've been reading they've switch to Jackson ... all of this has made googling for help pretty hit or miss.
From what I can tell, I need to implement a ContextResolver along the lines of:
public class JacksonConfig impelments ContextResolver<ObjectMapper>
{
private final OBjectMapper objectMapper;
public JacksonConfig() throws Exception
{
objectMapper = new ObjectMapper.configure(
SerializationFeature.WRITE_DATE_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> arg0)
{
return objectMapper;
}
}
The thing I haven't been able to find, is what do I do with this? Where do I put it?
So the larger questions are, am I heading in the right direction and are my assumptions correct?

You need to register your ContextResolver implementation with Resteasy. You can do this by annotating your class with the #Provider annotation and allowing Resteasy to automatically scan it during startup, registering it in web.xml, or registering it in a class that extends javax.ws.rs.core.Application (if that is how you are bootstrapping Resteasy).
Registering via Annotations
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper>
{
private final ObjectMapper objectMapper;
public JacksonConfig() throws Exception
{
objectMapper = new ObjectMapper.configure(
SerializationFeature.WRITE_DATE_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> arg0)
{
return objectMapper;
}
}
Verify that classpath scanning is enabled in your web.xml file like so:
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param>
NOTE: If you are deploying this in JBoss 7 do not set the resteasy.scan context parameter as it is enabled by default.
Registering via web.xml
Add the following context parameter to your web.xml file. The value of the parameter should be the fully qualified class name of your ContextResolver.
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>foo.contextresolver.JacksonConfig</paramvalue>
</context-param>
Registering via Application
If you are using an Application class to configure Resteasy you can add your provider to the set of services and providers to register with Resteasy like so:
public class MyApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(JacksonConfig.class);
set.add(MyService.class);
return set;
}
}
More on standalone configuration HERE

Using with the JSR310 (new api date) - LocalDate, LocalDateTime, LocalTime
Add dependence:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.4.0</version>
</dependency>
And create a provider to register the module:
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() throws Exception {
objectMapper = new ObjectMapper()
.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS )
.disable( SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS )
.setSerializationInclusion( JsonInclude.Include.NON_NULL )
.registerModule( new JSR310Module() );
}
#Override
public ObjectMapper getContext( Class<?> arg0 ) {
return objectMapper;
} }

Just annotate your fields with (note the string literal could be externalized/referred from a constant):
#javax.json.bind.annotation.JsonbDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
Date myDate;

Related

How to dynamic ObjectMapper implementation for spring

The client can decide whether the PropertyNamingStrategy is SNAKE_CASE or CAMEL_CASE by adding it to the api parameter.
my idea is to do aop interceptor before entering the controller to customize ObjectMapper.
I have setPropertyNamingStrategy for ObjectMapper object but it only get first PropertyNamingStrategy set, values set after first time are not used.
#Aspect
#Component
#RequiredArgsConstructor
public class NamingJsonAspect {
private final ObjectMapper objectMapper;
#Pointcut("execution(public * com.nnv98..*Controller.*(..))")
private void namingJson() {}
#SneakyThrows
#Before("namingJson()")
public void doAround(JoinPoint proceedingJoinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
String namingJson = request.getParameter("namingJson");
if(namingJson.equals("SNAKE_CASE")){
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
}else {
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
}
}
}
thank you
You should read the JavaDoc for ObjectMapper.
What you are seeing is expected behavior, as explained in the JavaDoc: configuration can only be done before first read/write usage. After first usage, changing the configuration may have no effect or may result in errors. The JavaDoc also explains how you can work around the limitation.

Dropwizard ObjectMapper not honouring enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)

I want to be able for jackson to parse case insensitive enums. For e.g
public enum OperType {
SUM
PRODUCT
}
i want to accept both "SUM" and "sum" in the POST request.
I am getting hold of objectMapper in Application::run and enabling the setting:
environment.getObjectMapper().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
But this is having no effect!
Jersey doesn't use objectMapper from Dropwizard bootstrap despite what Dropwizard's official documentation might lead one to believe.
Needed to register custom ContextResolver in the Application::run to make it work:
environment.jersey().register(new ObjectMapperContextResolver(injector.getInstance(ObjectMapper.class)));
where:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Man these documentations around the dropwizard ecosystem can be really confusing for someone who isn't as well versed yet!

Migration of JSONJAXBContext jersey 1.x to 2.x

I want to upgrade my jersey version to 2.x from 1.x.
In my code I had:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private static final Class<?>[] classes = {
A.class, B.class, C.class, D.class, E.class,
F.class, G.class
};
private JAXBContext context;
public JAXBContextResolver() throws Exception {
context = new JSONJAXBContext(JSONConfiguration.natural()
.humanReadableFormatting(true).rootUnwrapping(true).build(),
classes);
}
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
But JSONJAXBContext and JSONConfiguration are not defined in jersey 2.x.
How can I make the change accordingly?
The question Where did JSONConfiguration go in Jersey 2.5.x? is not answering my question because it does not explain how do I add my class which I want to return as output
There is no need for this. You either are going to use MOXy or Jackson as your JSON provider in Jersey 2.x. For the latter, you configure with MoxyJsonConfig. For Jackson, you use ObjectMapper. Figure out which provider you are using, and configure the according object. Both can be configured in a ContextResolver like you're currently doing.
As far as your current configurations
You won't need to configure any classes with either of these.
Unwrapped objects are serialized by default.
And to pretty print you would do the following
Jackson
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
MOXy
MoxyJsonConfig config = new MoxyJsonConfig()
.setFormattedOutput(true);

Jersey Exception mappers not working when jackson deserialization fails

I am using Jersey 2.10 with Jackson serialization/deserialization feature in my REST API.
My idea is to make my REST API to always return a standard JSON error response. For that I have ExceptionMapper classes that build proper json error responses for any exception being thrown in the Jersey application. I also have a jsp which produces the same kind of JSON response, which I registered as error-page in the web.xml that covers all the errors that could come before Jersey being loaded.
But there is one case in which neither my Exception mappers nor my json producing jsp are working, that is when sending a bad formed json to a POST REST endpoint which just returns the following message:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Content-Type: text/plain
Content-Length: 210
Date: Tue, 24 Jun 2014 22:14:11 GMT
Connection: close
Can not deserialize instance of com.example.rest.User[] out of START_OBJECT token
at [Source: org.glassfish.jersey.message.internal.EntityInputStream#1dcccac; line: 1, column: 1]
How can I make Jersey to return my custom error response instead of this?
UPDATE:
Based on the answer by #Lucasz, I did more research and found that there are two Exception mappers defined inside the package com.fasterxml.jackson.jaxrs.base (https://github.com/FasterXML/jackson-jaxrs-providers/tree/master/base/src/main/java/com/fasterxml/jackson/jaxrs/base) JsonMappingExceptionMapper and JsonParseExceptionMapper that seem to be shadowing my custom mappers.
How can I unregister those mappers?
This is how I am currently registering the mappers:
#ApplicationPath("/")
public class MyApp extends ResourceConfig{
public SyntheticAPIApp() {
packages("com.example.resource", "com.example.mapper");
register(org.glassfish.jersey.jackson.JacksonFeature.class);
}
}
I tested it with an exception mapper like below:
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.core.JsonProcessingException;
#Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException>{
public static class Error {
public String key;
public String message;
}
#Override
public Response toResponse(JsonProcessingException exception) {
Error error = new Error();
error.key = "bad-json";
error.message = exception.getMessage();
return Response.status(Status.BAD_REQUEST).entity(error).build();
}
}
and it worked.
Update: changed JsonParseException to JsonProcessingException (more general)
Update2:
In order to avoid registering the unwanted mappers replace
register(org.glassfish.jersey.jackson.JacksonFeature.class);
with
register(com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class);
Look at the source code of JacksonFeature and you'll understand what's happening.
Starting with Jersey 2.26 (1, 2) it should be enough to annotate the custom exception mapper with a sufficiently high Priority (high here meaning a low, strictly positive number). To override the “default” mappers provided by org.glassfish.jersey.media:jersey-media-json-jackson (to register(JacksonFeature.class)) we only provide these two custom mappers:
#Provider
#Priority(1)
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
/* ... */
}
#Provider
#Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
/* ... */
}
Unfortunately JAX-RS 2 Spec disregards priorities and only states:
When choosing an exception mapping provider to map an exception,
an implementation MUST use the provider whose generic type is the nearest superclass of the exception.
Not registering JacksonFeature.class and registering JacksonJaxbJsonProvider.class instead as mentioned in another answer did not lead to consistent results.
I had the same problem, and the previous answer led me to the solution, but was not forking for me current Jersey (2.22). At first, I needed to use the org.glassfish.jersey.spi.ExtendedExceptionMapper like described in https://jersey.java.net/documentation/latest/representations.html.
Furthermore, Jersey is checking for an exception mapper, which is as close as possible to the thrown exception (from org.glassfish.jersey.internal.ExceptionMapperFactory):
for (final ExceptionMapperType mapperType : exceptionMapperTypes) {
final int d = distance(type, mapperType.exceptionType);
if (d >= 0 && d <= minDistance) {
final ExceptionMapper<T> candidate = mapperType.mapper.getService();
if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) {
mapper = candidate;
minDistance = d;
if (d == 0) {
// slight optimization: if the distance is 0, it is already the best case, so we can exit
return mapper;
}
}
}
}
Therefore I needed to map exactly the exception and not a more general exception.
In the end, my provider looks as follows:
#Provider
public final class JsonParseExceptionExceptionHandler implements ExtendedExceptionMapper<JsonParseException> {
#Override
public Response toResponse(final JsonParseException exception) {
exception.printStackTrace();
return Response.status(Response.Status.BAD_REQUEST).entity("JSON nicht in korrektem Format.").build();
}
#Override
public boolean isMappable(final JsonParseException arg0) {
return true;
}
}
I used "jackson-jaxrs-json-provider 2.8.8" and JAX-RS 2.0
Application class - you needs to register your ExceptionMapper implementation class:
#ApplicationPath("pathApplication")
public class ApplicationConfiguration extends Application{
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(YourJAXRSClass.class);
resources.add(JsonJacksonEM.class); //ExceptionMapper class implementation
//others resources that you need...
return resources;
}
}
ExceptionMapper class implementation:
#Provider
public class JsonJacksonEM implements ExceptionMapper<JsonParseException>{
#Override
public Response toResponse(JsonParseException exception) {
//you can return a Response in the way that you want!
return Response.ok(new YourObject()).build();
}
}
I had the same problem and solve overriding the ExceptionMapper. Perfect! One extra thing that I needed to do and were not understanding 100% was how to override the JacksonProvider for my application (I don't know if it was related to Jersey's version that I was using - 2.19). Here's my web.xml part that overrides it:
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
</param-value>
</init-param>
I have faced this issue recently, and the solution of registering com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider doesn't helped much. When I dig deeper I came to know that Jersey by default org.glassfish.jersey.jackson.JacksonFeature is registered by Jersey, if the dependency jersey-media-json-jackson even without the explicit declaration of registering it(Don't sure from which version the auto register is implemented, I guess atleast from Jersey 2.29) present in the classpath.
The JacksonFeature inturn registers JsonParseExceptionMapper and JsonMappingExceptionMapper automatically. Because of these default JSON exception mappers, all JSON related exceptions are not redirected to the custom exception mapper
Fortunately, Jersey 2.29.1 added support for registering JacksonFeature without the exception handlers. link feature request link, code changes.
#Provider
public class ApplicationConfig extends Application {
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = super.getSingletons();
singletons.add(JacksonFeature.withoutExceptionMappers());
return singletons;
}
}
The above code snippet will override the default JacksonFeature registered by Jersey. By doing so, all JSON-related exceptions will be redirected to custom exception mappers present in the application.

Jersey 2.6, Spring 4: Context Resolver with #Component doesn't work

Based on other issues that were actually resolved in Jersey 2.6, I suspect this might be a Jersey bug, but I wanted to vet it here first.
The following works as expected:
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper;
#Value("${json.prettyPrint}")
private boolean prettyPrint = false;
public ObjectMapperResolver() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, prettyPrint);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
/**
* Push Joda de/serializers into the actual mapper
*/
#PostConstruct
private void configureJodaSerializers() {
mapper.registerModule(new JodaModule()
// Our deserializers are more forgiving
.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer())
.addDeserializer(LocalTime.class, new CustomLocalTimeDeserializer())
// Custom serializer to avoid HH:mm:ss.SSS (we don't want millis)
.addSerializer(LocalTime.class, new LocalTimeSerializer()));
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
In all my resources, my Joda date types are properly serialized. However, I wanted to grab the same ObjectMapper to use in a non-Jersey managed context (outside of my resources), so I added #Component to the above class with the intention of auto-wiring it elsewhere. After adding #Component (org.springframework.stereotype.Component), Jersey no longer picks up the ObjectMapper from the resolver and my date serialization goes back to the defaults.
Unless I completely misunderstand the annotations, I don't think giving Spring control of the life-cycle should impede Jersey's ability to pick up my resolver. Additionally worth noting is the fact that when we were on Jersey 1.9, we HAD to have #Component on there or else it would not get picked up. In order to get our upgrade from 1.9 to 2.6 working, I actually had initially removed it, but was hoping to put it back.
From my pom:
Java 1.7
Jackson 2.3.1
Jersey 2.6
Joda 2.1
Spring 4.0.1-RELEASE
I had a similar issue with a similar setup as your one.
While probably there's something wrong in Jersey 2.x Spring integration beahviour, i think you can do the follow:
Declare the object mapper as a Spring bean, so you can inject it via spring where you need it:
#Component
public class ObjectMapperBean extends ObjectMapper {
public ObjectMapperBean() {
super();
// Configuration here...
}
}
Then you write a Jersey context resolver for it:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
#Autowired
private ObjectMapperBean objectMapper;
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
Even if not declared as a component you will get the ObjectMapperBean injected in it.
Hope it helps!

Categories

Resources