java jersey jackson jax-rs jersey-2.0

java - Custom ObjectMapper con Jersey 2.2 y Jackson 2.1



jax-rs jersey-2.0 (7)

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

Dependencias POM:

<dependencies> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-grizzly2-servlet</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.1.4</version> </dependency> </dependencies>

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<ObjectMapper> { 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


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

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; } }

Las únicas dependencias de POM relevantes son:

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; } }


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 .


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<ObjectMapper>{ 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; } }


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/-answer

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); } } }


La siguiente solución se aplica a la siguiente pila (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 :

<dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.4.1</version> </dependency>

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

<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> </dependency>

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); } }


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.

@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<Object> getSingletons() { Set<Object> 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<String, Object> getProperties() { Map<String, Object> map = new HashMap<>(); log.log(Level.INFO, "Disabling MOXy JSON provider"); map.put("jersey.config.disableMoxyJson.server", true); return map; } @Override public Set<Class<?>> getClasses() { Set<Class<?>> 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<Class<?>> 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); }

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í:

<dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>


Por favor hacer esto:

1) añadir dependencia pom.xml

<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.2</version> </dependency>

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<ObjectMapper> { 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; } }