Custom ObjectMapper con Jersey 2.2 y Jackson 2.1

Estoy luchando con una aplicación REST con Grizzly, Jersey y Jackson, porque Jersey ignora mi ObjectMapper personalizado.

Dependencias POM:

  org.glassfish.jersey.containers jersey-container-grizzly2-servlet 2.2   com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider 2.1.4   

Las versiones resultantes son: Grizzly 2.3.3, Jackson 2.1.4 y Jersey 2.2.

Clase principal (quiero un registro explícito de los componentes de Jersey):

 public class Main { public static void main(String[] args) { try { ResourceConfig rc = new ResourceConfig(); rc.register(ExampleResource.class); rc.register(ObjectMapperResolver.class); HttpHandler handler = ContainerFactory.createContainer( GrizzlyHttpContainer.class, rc); URI uri = new URI("http://0.0.0.0:8080/"); HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri); ServerConfiguration config = server.getServerConfiguration(); config.addHttpHandler(handler, "/"); server.start(); System.in.read(); } catch (ProcessingException | URISyntaxException | IOException e) { throw new Error("Unable to create HTTP server.", e); } } } 

ContextResolver para ObjectMapper:

 @Provider @Produces(MediaType.APPLICATION_JSON) public class ObjectMapperResolver implements ContextResolver { private final ObjectMapper mapper; public ObjectMapperResolver() { System.out.println("new ObjectMapperResolver()"); mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); } @Override public ObjectMapper getContext(Class type) { System.out.println("ObjectMapperResolver.getContext(...)"); return mapper; } } 

No se llama al constructor ObjectMapperResolver ni a getContext . ¿Qué me estoy perdiendo? Preferiría usar Jersey 2.2 y Jackson 2.1, porque es una dependencia de otra lib.

Se puede encontrar un ejemplo completo en GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow

La siguiente solución se aplica a la siguiente stack (como en … esta es la configuración que he usado para probarla)

Jersey 2.12, Jackson 2.4.x

Estoy agregando mi mensaje con la solución que he presentado en esta publicación, ya que fue bastante relevante para las muchas búsquedas de Google que he presentado hoy … Es una solución engorrosa para lo que creo que es una problema aún más engorroso.

1. Asegúrate de que tu configuración jackson-jaxrs-json-provider CONTIENE la jackson-jaxrs-json-provider :

  com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider 2.4.1  

2. Asegúrate de que tu configuración de administrador NO CONTIENE la dependencia de jersey-media-json-jackson :

  org.glassfish.jersey.media jersey-media-json-jackson  

3. Cree un componente com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider que com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider manera:

 import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.Provider; @Provider @Produces(MediaType.APPLICATION_JSON) public class CustomJsonProvider extends JacksonJaxbJsonProvider { private static ObjectMapper mapper = new ObjectMapper(); static { mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enable(SerializationFeature.INDENT_OUTPUT); } public CustomJsonProvider() { super(); setMapper(mapper); } } 

Como puede observar, también es aquí donde definimos la instancia personalizada de com.fasterxml.jackson.databind.ObjectMapper

4. Extienda javax.ws.rs.core.Feature través de MarshallingFeature como tal:

 import javax.ws.rs.core.Feature; import javax.ws.rs.core.FeatureContext; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; public class MarshallingFeature implements Feature { @Override public boolean configure(FeatureContext context) { context.register(CustomJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class); return true; } } 

5. org.glassfish.jersey.server.ResourceConfig registrar este proveedor personalizado de esa manera, siempre que configure su aplicación a través de org.glassfish.jersey.server.ResourceConfig manera:

 import org.glassfish.jersey.server.ResourceConfig; ... public class MyApplication extends ResourceConfig { public MyApplication() { ... register(MarshallingFeature.class); ... } } 

Otras notas y observaciones:

  1. Esta solución se aplica tanto si usa javax.ws.rs.core.Response para envolver las respuestas de su controlador o no.
  2. Asegúrese de tener en cuenta cuidadosamente (copiar / pegar) los siguientes fragmentos de código, ya que los únicos bits “no obligatorios”, por así decirlo, son los que se com.fasterxml.jackson.databind.ObjectMapper la configuración personalizada de com.fasterxml.jackson.databind.ObjectMapper .

@jcreason

Perdón por dejar caer la pelota en este @jcreason, espero que todavía seas curioso. Así que revisé el código del año pasado y esto es lo que surgió para proporcionar un mapeador personalizado.

El problema fue que durante la inicialización de funciones cualquier mapeador de objetos personalizado se deshabilita por algún código en

org.glassfish.jersey.jackson.JacksonFeature: 77 (jersey-media-json-jackson-2.12.jar)

 // Disable other JSON providers. context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE); 

Pero esta característica solo se registra por este componente

org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable

 if (!context.getConfiguration().isRegistered(JacksonFeature.class)) { context.register(JacksonFeature.class); } 

Entonces, lo que hice fue registrar mi propia característica, que registra mi propio proveedor de mapeadores de objetos y descartar un viaje que impidió que org.glassfish.jersey.jackson.JacksonFeature se registrara y anulara mi asignador de objetos …

 import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper; import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper; import org.glassfish.jersey.internal.InternalProperties; import org.glassfish.jersey.internal.util.PropertiesHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.core.Configuration; import javax.ws.rs.core.Feature; import javax.ws.rs.core.FeatureContext; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; public class MarshallingFeature implements Feature { private final static String JSON_FEATURE = MarshallingFeature.class.getSimpleName(); @Override public boolean configure(FeatureContext context) { context.register(JsonParseExceptionMapper.class); context.register(JsonMappingExceptionMapper.class); context.register(JacksonJsonProviderAtRest.class, MessageBodyReader.class, MessageBodyWriter.class); final Configuration config = context.getConfiguration(); // Disables discoverability of org.glassfish.jersey.jackson.JacksonFeature context.property( PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE); return true; } } 

Y aquí está el proveedor de asignador de objetos personalizado …

 import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.Provider; @Provider @Produces(MediaType.APPLICATION_JSON) public class JacksonJsonProviderAtRest extends JacksonJaxbJsonProvider { private static ObjectMapper objectMapperAtRest = new ObjectMapper(); static { objectMapperAtRest.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapperAtRest.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapperAtRest.configure(SerializationFeature.INDENT_OUTPUT, true); // Different from default so you can test it :) objectMapperAtRest.setSerializationInclusion(JsonInclude.Include.ALWAYS); } public JacksonJsonProviderAtRest() { super(); setMapper(objectMapperAtRest); } } 

Encontré una solución. Tuve que instanciar el proveedor de Jackson por mi cuenta y configurar mi ObjectMapper personalizado. Un ejemplo de trabajo se puede encontrar en GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-withour

ObjectMapperResolver mi ObjectMapperResolver y modifiqué mi método main :

 public class Main { public static void main(String[] args) { try { // create custom ObjectMapper ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); // create JsonProvider to provide custom ObjectMapper JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); provider.setMapper(mapper); // configure REST service ResourceConfig rc = new ResourceConfig(); rc.register(ExampleResource.class); rc.register(provider); // create Grizzly instance and add handler HttpHandler handler = ContainerFactory.createContainer( GrizzlyHttpContainer.class, rc); URI uri = new URI("http://0.0.0.0:8080/"); HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri); ServerConfiguration config = server.getServerConfiguration(); config.addHttpHandler(handler, "/"); // start server.start(); System.in.read(); } catch (ProcessingException | URISyntaxException | IOException e) { throw new Error("Unable to create HTTP server.", e); } } } 

Por favor hacer esto:

1) añadir dependencia pom.xml

  org.glassfish.jersey.media jersey-media-json-jackson 2.2  

2) registrar JacksonFeature en Main.java

 public class Main { public static void main(String[] args) { try { ResourceConfig rc = new ResourceConfig(); rc.register(ExampleResource.class); rc.register(ObjectMapperResolver.class); rc.register(JacksonFeature.class); HttpHandler handler = ContainerFactory.createContainer( GrizzlyHttpContainer.class, rc); URI uri = new URI("http://0.0.0.0:8080/"); HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri); ServerConfiguration config = server.getServerConfiguration(); config.addHttpHandler(handler, "/"); server.start(); System.in.read(); } catch (ProcessingException | URISyntaxException | IOException e) { throw new Error("Unable to create HTTP server.", e); } } } 

3) Usa org.codehaus.jackson.map.ObjectMapper en tu recurso

 import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig.Feature; @Provider @Produces(MediaType.APPLICATION_JSON) public class ObjectMapperResolver implements ContextResolver { private final ObjectMapper mapper; public ObjectMapperResolver() { System.out.println("new ObjectMapperResolver()"); mapper = new ObjectMapper(); mapper.enable(Feature.INDENT_OUTPUT); } @Override public ObjectMapper getContext(Class type) { System.out.println("ObjectMapperResolver.getContext(...)"); return mapper; } } 

Como me tomó algunas horas lograr que esto funcione con Java EE7 y Glassfish4, esta es mi solución:

 @javax.ws.rs.ApplicationPath("withJackson") public class ApplicationConfig extends Application { private static final Logger log = java.util.logging.Logger.getLogger(ApplicationConfig.class.getName()); @Override public Set getSingletons() { Set set = new HashSet<>(); log.log(Level.INFO, "Enabling custom Jackson JSON provider"); set.add(new JacksonJsonProvider().configure(SerializationFeature.INDENT_OUTPUT, true)); return set; } @Override public Map getProperties() { Map map = new HashMap<>(); log.log(Level.INFO, "Disabling MOXy JSON provider"); map.put("jersey.config.disableMoxyJson.server", true); return map; } @Override public Set> getClasses() { Set> resources = new java.util.HashSet<>(); addRestResourceClasses(resources); return resources; } /** * Do not modify addRestResourceClasses() method. * It is automatically populated with * all resources defined in the project. * If required, comment out calling this method in getClasses(). */ private void addRestResourceClasses(Set> resources) { resources.add(com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper.class); resources.add(com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper.class); resources.add(com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper.class); resources.add(com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper.class); resources.add(de.lathspell.java_test_ee7_json.Api.class); resources.add(de.lathspell.java_test_ee7_json.with_jackson.MyExceptionMapper.class); } 

Las únicas dependencias de POM relevantes son:

   com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider 2.2.3   com.fasterxml.jackson.core jackson-databind 2.2.3   javax javaee-web-api 7.0 provided  

Me di cuenta de esto, basándome en un poco de retoques.

El problema parece estar en el mecanismo de autodetección de funciones de Jersey. Si confía en Jersey para cargar JacksonJaxbJsonProvider, entonces se ignora el proveedor de contexto personalizado para su ObjectMapper. Si, en cambio, registras la función manualmente, funciona. Presumo que esto tiene que ver con que el proveedor detectado automáticamente se cargue en un contexto de contexto diferente, pero en cuanto a una solución, aquí es a lo que terminé. Tenga en cuenta que lo envolví en una característica, debería poder registrarlo directamente con su aplicación sin ningún problema.

 public final class RequestMappingFeature implements Feature { @Override public boolean configure(final FeatureContext context) { context.register(ObjectMapperProvider.class); // If you comment out this line, it stops working. context.register(JacksonJaxbJsonProvider.class); return true; } } 

ACTUALIZACIÓN Noviembre 2017 : Las cosas han cambiado un poco en el mundo Jersey2. Si lo anterior no funciona, intente esto:

El nuevo método para proporcionar su propio ObjectMapper ahora se ve así:

 public final class JacksonFeature implements Feature { private static final ObjectMapper MAPPER; static { // Create the new object mapper. MAPPER = new ObjectMapper(); // Enable/disable various configuration flags. MAPPER.configure( DeserializationFeature.READ_ENUMS_USING_TO_STRING, true); // ... Add your own configurations here. } @Override public boolean configure(final FeatureContext context) { JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider( MAPPER, DEFAULT_ANNOTATIONS); context.register(provider); return true; } } 

De Jersey 2.17 documentos: https://jersey.github.io/documentation/2.17/user-guide.html#jackson-registration

En la aplicacion

 @ApplicationPath("/") public class MyApplication extends ResourceConfig { public MyApplication() { register(JacksonFeature.class); // This is the class that you supply, Call it what you want register(JacksonObjectMapperProvider.class); //... } } 

Editar, se olvidó de agregar el JacksonObjectMapperProvider que proporciona en el registro (..):

 import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; @Provider public class JacksonObjectMapperProvider implements ContextResolver{ final ObjectMapper defaultObjectMapper; public JacksonObjectMapperProvider() { defaultObjectMapper = createDefaultMapper(); } @Override public ObjectMapper getContext(Class type) {return defaultObjectMapper;} public static ObjectMapper createDefaultMapper() { final ObjectMapper jackson = new ObjectMapper(); // any changes to the ObjectMapper is up to you. Do what you like. // The ParameterNamesModule is optional, // it enables you to have immutable POJOs in java8 jackson.registerModule(new ParameterNamesModule()); jackson.enable(SerializationFeature.INDENT_OUTPUT); jackson.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS); jackson.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); return jackson; } } 

Con Jackson 2.7, hacer esto no funcionó:

 public class MyApplication extends ResourceConfig { public MyApplication() { register(MyObjectMapperProvider.class); }} 

Se llamó al constructor MyObjectMapperProvider, pero nunca se llamó a getContext () .

El registro de MyObjectMapperProvider en el constructor super () lo hace funcionar:

 public class MyApplication extends ResourceConfig { public MyApplication() { super( // register Jackson ObjectMapper resolver MyObjectMapperProvider.class ); }} 

Vea este código de ejemplo Jersey .