Whome
Whome

Reputation: 10400

Reuse Jackson ObjectMapper and JsonFactory instances

(Jackson 2.6.1, Jersey 2.21.0)

My webapp uses JAX-RS interfaces and Jackson json helper classes. I do not directly import Jersey classes, plan on keeping it irrelevant to my app.

I want to reuse ObjectMapper and JsonFactory instances as Jackson docs recommend, but unable to get reference to them inside MyBeanService class.

// resolver returns null
ContextResolver<ObjectMapper> resolver = providers.getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
ContextResolver<JacksonJaxbJsonProvider> resolverB = providers.getContextResolver(JacksonJaxbJsonProvider.class, MediaType.WILDCARD_TYPE);
// returns org.glassfish.jersey.server.ResourceConfig$WrappingResourceConfig wrapper
// also I wish not to import directly any jersey classes
ContextResolver<Application> resolverC = providers.getContextResolver(Application.class, MediaType.WILDCARD_TYPE);

// resolver was null so getters don't work
ObjectMapper mapper = resolver.getContext(ObjectMapper.class);
JsonGenerator jsonG = mapper.getFactory().createGenerator(os, JsonEncoding.UTF8);

Should I just provide public static ObjectMapper MyApplication.getObjectMapper() function? MyApplication would set private static objectmapper field in getSingletons() method?

This is my configuration and classes to provide restjson servlet path.

mywebapp/WEB-INF/web.xml

Application instance given in web.xml file.

...
<servlet><servlet-name>myapp.rest.MyApplication</servlet-name></servlet>
<servlet-mapping>
   <servlet-name>myapp.rest.MyApplication</servlet-name>
   <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

MyApplication.java

Set rest services and configure objectmapper instance such as datetime field format.

package myapp.rest;

import java.util.*;
import java.text.*;
import javax.ws.rs.core.Application;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.cfg.Annotations;

public class MyApplication extends Application {

    @Override public Set<Class<?>> getClasses() {
        Set<Class<?>> list = new HashSet<Class<?>>();
        list.add(MyBeanService.class);
        return list;
    }

    @Override public Set<Object> getSingletons() {
        Set<Object> list = new HashSet<Object>();

        ObjectMapper mapper = new ObjectMapper();       
        mapper.disable(SerializationFeature.INDENT_OUTPUT);
        //mapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // enable=1433435279692 (utcMillis)
        //mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // disable=2015-06-04T16:25:27.056+0000 (ISO8601_utc) 
        DateFormat dtf = SimpleDateFormat.getDateTimeInstance();
        ((SimpleDateFormat)dtf).applyPattern("yyyy-MM-dd'T'HH:mm:ssZ"); // 2015-06-04T19:25:27+0300 (custom+tzOffset)
        mapper.setDateFormat(dtf);
        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(
                mapper, 
                new Annotations[] { Annotations.JACKSON, Annotations.JAXB } );
        list.add(provider);     
        return list;
    }

}

MyBeanService.java

This is a service class handling rest url paths.

import java.io.*;
import java.util.*;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;

@Path("") @Singleton
public class MyBeanService {
    //@Context private Providers providers;
    //@Context private Application application;

    @GET @Path("/{serverName}/beans/{id}")
    @Produces({"application/json;charset=UTF-8"})
    public Response getBean (
            @Context HttpServletRequest req,
            @PathParam("serverName") String serverName,
            @PathParam("id") long id) {

        final MyServer server= JPADB.getServer(serverName);
        final MyBean bean    = JPADB.getBean(server, id);

        StreamingOutput stream = new StreamingOutput() {
           @Override public void write(OutputStream os) throws IOException, WebApplicationException {
              //*** I want to reuse JsonFactory and ObjectMapper, but
              // currently create new factory each time
              JsonFactory jsonF = new JsonFactory();
              JsonGenerator jsonG = jsonF.createGenerator(os, JsonEncoding.UTF8);
              try {
                 jsonG.writeStartObject();
                 jsonG.writeNumberField("id", bean.getId());
                 jsonG.writeStringField("title", bean.getTitle());
                 jsonG.writeStringField("serverId", server.getId());
                 jsonG.writeStringField("serverName", server.getName());
                 ..various json fields, some optional, some at runtime autogen..
                 jsonG.writeEndObject();
                 jsonG.flush();
              } finally {
                 jsonG.close();
              }
           }
        };

        CacheControl cc = new CacheControl();
        cc.setNoCache(true);
        return Response.ok().type("application/json;charset=UTF-8")
                .cacheControl(cc).entity(stream).build();
    }
}

Upvotes: 2

Views: 4031

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 209022

The ContextResolver is null because you don't actually have a ContextResolver. This is something you need to write yourself.

@Provider
@Produces("application/json")
@Consumes("application/json")
public class MapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public MapperContextResolver() {
        mapper = new ObjectMapper();
        // do any configurations to mapper
    }

    @Override
    public ObjectMapper getContext(Class<?> cls) {
        return mapper;
    }
}

Then just register it with the application. How the Jackson(Jaxb)JsonProvider works is that first it looks for an ObjectMapper that was passed to the constructor. If not found, then it looks for a ContextResolver for the ObjectMapper. If not found, it will just use its own ObjectWriter and ObjectReader.

So you don't need to create the JacksonJaxbJsonProvider with the ObjectMapper. Just use the default constructor, and the provider will look for your resolver.

Upvotes: 4

Related Questions