Custom ObjectMapper Avec Jersey 2.2 et Jackson 2.1

je me bats avec une application de repos avec Grizzly, Jersey et Jackson, parce que Jersey ignore mon ObjectMapper personnalisé.

POM dépendances:

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

les versions résultantes sont: Grizzly 2.3.3, Jackson 2.1.4 et Jersey 2.2.

classe Principale (je veux explicite de l'enregistrement de Jersey composants):

java prettyprint-override">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 for 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;
    }

}

ni L'un ni l'autre ObjectMapperResolver constructeur ni getContext se faire appeler. Que suis-je manquant? Je préfère utiliser Jersey 2.2 et Jackson 2.1, parce que c'est une dépendance pour une autre lib.

Un exemple complet peut être trouvé sur GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow

35
demandé sur Joshua Taylor 2013-09-18 16:58:49

7 réponses

La solution suivante s'applique à la pile suivante (comme dans... c'est la configuration que j'ai utilisé pour tester)

Maillot 2.12, Jackson 2.4.x

j'ajoute mon message avec la solution que j'ai trouvée sur ce post car il était tout à fait pertinent pour les nombreuses recherches Google que j'ai mis dans aujourd'hui... C'est une solution lourde à ce que je considère comme un problème encore plus lourd.

1. Assurez-vous que votre configuration maven contient le jackson-jaxrs-json-provider dépendance:

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

2. Assurez-vous que votre configuration maven ne contient pas le jersey-media-json-jackson dépendance:

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

3. Créer un @Provider Elargissement du composant com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider comme ceci:

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

comme vous pouvez le voir, c'est aussi là que nous définissons l'instance personnalisée de com.fasterxml.jackson.databind.ObjectMapper

4. Étendre javax.ws.rs.core.Feature par MarshallingFeature comme ceci:

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. Vous devez enregistrer ce fournisseur personnalisé comme si, à condition de configurer votre application à l'aide de org.glassfish.jersey.server.ResourceConfig comme ceci:

import org.glassfish.jersey.server.ResourceConfig;
...

public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...
        register(MarshallingFeature.class);
        ...
     }
 }

Autres notes et observations:

  1. Cette solution s'applique que vous utilisiez javax.ws.rs.core.Response pour envelopper les réponses de votre contrôleur ou non.
  2. s'il Vous Plaît assurez-vous que vous prenez soigneusement en considération (copier/coller) les extraits de code suivants puisque les seuls" non-obligatoires " pour ainsi dire bits sont ceux concernant la configuration personnalisée de la com.fasterxml.jackson.databind.ObjectMapper.

@jcreason

Désolé pour déposer le ballon sur ce @jcreason, j'espère que vous êtes encore curieux. Donc j'ai vérifié le code de l'année dernière et c'est ce que j'ai trouvé pour fournir un mapper personnalisé.

le problème est que lors de l'initalisation de la fonctionnalité, tout mappeur d'objet personnalisé est désactivé par un code dans

org.glassfish.Jersey.Jackson.JacksonFeature: 77 (jersey-médias-json-jackson-2.12.jar)

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

mais cette fonctionnalité n'est enregistrée que par ce composant

org.glassfish.Jersey.Jackson.interne.Jacksonautodiscutable

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

alors ce que j'ai fait était d'Enregistrer ma propre fonctionnalité qui enregistre mon propre fournisseur de mapper d'objet et tombe dans un fil de trip arrêt org.glassfish.Jersey.Jackson.JacksonFeature d'être enregistré et de passer outre mon objet cartographe...

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

Et voici le fournisseur custom object mapper...

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);
    }
}
34
répondu Filip 2016-09-06 06:57:20

j'ai trouvé une solution. J'ai eu pour instancier le Jackson Fournisseur par moi-même et de mettre mon custom ObjectMapper. Un exemple pratique se trouve sur GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer

j'ai supprimé mon ObjectMapperResolver et modifié ma main méthode:

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);
        }
    }
}
26
répondu svenwltr 2013-09-20 13:22:00

Merci de faire ceci:

1) Ajouter pom.dépendance xml

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

2) enregistrer JacksonFeature dans la 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) Utiliser org.codehaus.Jackson.cartographie.ObjectMapper dans votre ressource

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;
    }
}
8
répondu alexey 2013-09-18 23:43:46

j'ai trouvé ça, basé sur un bricolage.

le problème semble être dans le mécanisme de détection automatique de la fonctionnalité de Jersey. Si vous comptez sur Jersey pour charger le JacksonJaxbJsonProvider, alors le fournisseur de contexte personnalisé pour votre ObjectMapper est ignoré. Si, à la place, vous enregistrez manuellement la fonctionnalité, cela fonctionne. Je pose l'hypothèse que cela a à voir avec le fournisseur autodétecté étant chargé dans un cadre de contexte différent, mais pour une solution, voici ce que j'ai fini avec. Note que j'ai enveloppé dans une fonction, vous devez être capable de l'enregistrer directement avec votre demande sans problème.

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

mise à jour novembre 2017: les choses ont un peu changé dans le monde de Jersey2. Si le ci-dessus ne fonctionne pas, essayez ceci:

La nouvelle méthode de fournir votre propre ObjectMapper ressemble maintenant à ceci:

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;
    }
}
8
répondu krotscheck 2017-11-16 23:23:49
@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);
}

les seules dépendances de POM pertinentes sont:

    <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>
7
répondu lathspell 2014-04-02 02:57:47

dans le Jersey 2.17 docs: https://jersey.github.io/documentation/2.17/user-guide.html#jackson-registration

Dans l'application

@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);
    //...
  }
}

Edit, oublié d'ajouter le JacksonObjectMapperProvider que vous fournissez dans register(..):

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;
   }
}
4
répondu George 2018-01-17 18:47:55

Avec Jackson 2.7, faire ce qui n'a pas fonctionné:

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

MyObjectMapperProvider constructeur a été appelé, mais getContext () n'a jamais été appelé.

enregistrer MyObjectMapperProvider dans super () constructor make it work:

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

Voir ce Maillot exemple de code.

0
répondu c-toesca 2016-04-08 20:17:57