web-dev-qa-db-fra.com

Utilisation de Jackson ObjectMapper avec Jersey

J'utilise Jersey 2.4 pour créer une simple interface REST qui sert un objet JSON. Mon problème est que j'essaie d'utiliser les annotations Jackson plus rapides de XML pour contrôler la sortie et ce n'est pas travailler pour moi. J'ai mis les annotations dans ma classe de bean mais elles sont ignorées.

Lorsque je crée explicitement un ObjectMapper et que je l'utilise pour stringifier le Java bean, j'obtiens la sortie que je veux, qui respecte les annotations de Jackson. Cependant, je préfère ne pas avoir à effectuez cette étape pour que ma classe de ressources puisse simplement retourner le bean et que le framework Jersey se charge de le stringifier.

J'ai essayé de résoudre ce problème en utilisant la réponse de ObjectMapper personnalisé avec Jersey 2.2 et Jackson 2.1 , cependant, cela ne semble pas fonctionner pour moi. Je vois que le ContextResolver est créé mais il n'est jamais appelé.

J'ai également passé de nombreuses heures à essayer de résoudre ce problème apparemment simple. J'ai réduit cela à un cas de test très simple, qui est illustré ci-dessous. J'apprécierais toute aide pour résoudre ce problème.

Ressource Java:

@Path("resource")
public class MainResource {

    public static class Foobar {
        @JsonIgnore
        private String foo = "foo";
        private String baa = "baa";
        private Map<String, List<? extends Number>> map = new HashMap<>();

        public Foobar() {
            map.put("even", Arrays.asList(new Integer[] { 2, 4, 6, 8, 10 }));
            map.put("odd", Arrays.asList(new Integer[] { 1, 3, 5, 7, 9 }));
            map.put("float", Arrays.asList(new Float[] { 1.1F, 2.2F, 3.3F }));
        }

        public String getFoo() {
            return foo;
        }

        public void setFoo(String foo) {
            this.foo = foo;
        }

        public String getBaa() {
            return baa;
        }

        public void setBaa(String baa) {
            this.baa = baa;
        }

        @JsonAnyGetter
        public Map<String, List<? extends Number>> getMap() {
            return map;
        }

        public void setMap(Map<String, List<? extends Number>> map) {
            this.map = map;
        }
    }

    private ObjectMapper om = new ObjectMapper();

    @GET
    @Path("get-object")
    @Produces(MediaType.APPLICATION_JSON)
    public Foobar getObject() {
        // In this method, I simply return the bean object but the WRONG JSON syntax is generated.
        return new Foobar();
    }

    @GET
    @Path("get-string")
    @Produces(MediaType.APPLICATION_JSON)
    public String getString() throws JsonProcessingException {
        // This method returns the RIGHT JSON syntax but I don't want to have to explicitly use the ObjectMapper.
        Foobar foobar = new Foobar();
        return om.writeValueAsString(foobar);
    }
}

web.xml:

<web-app version="3.0" xmlns="http://Java.Sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://Java.Sun.com/xml/ns/javaee http://Java.Sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <module-name>sample</module-name>

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>ie.cit.nimbus.sample</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

Dépendances POM:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-spring3</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.4.1</version>
    </dependency>
</dependencies>
14
jcstockdale

[~ # ~] edit [~ # ~] : N'utilisez pas l'ancienne approche ci-dessous car cela produit bogues (au moins avec Android, voir EDIT2 pour plus de détails). D'après mes tests, Jersey v2.6 semble résoudre le problème avec le @Provide, quelle approche n'a pas fonctionné. J'ai pu le faire fonctionner avec ce simple fournisseur:

@Provider
public class JerseyMapperProvider implements ContextResolver<ObjectMapper> {
    private static ObjectMapper apiMapper = ObjectMapperManager.createMapperForApi();
    @Override
    public ObjectMapper getContext(Class<?> type)
    {
        return apiMapper;
    }
}

Alors s'il vous plaît, n'utilisez pas mon hack d'en bas.


ANCIENNE APPROCHE

En utilisant

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>

ne fonctionnait pas pour moi (Jersey 2.4 et Jackson 2.3) et cela est peut-être dû à un bug signalé par le fournisseur jackson dans le code où le ContextResolver devrait être enregistré dans JacksonJsonProvider.Java (2.3rc1):

 @Override
protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType)
{
    if (_providers != null) {
        ContextResolver<ObjectMapper> resolver = _providers.getContextResolver(ObjectMapper.class, mediaType);
        /* Above should work as is, but due to this bug
         *   [https://jersey.dev.Java.net/issues/show_bug.cgi?id=288]
         * in Jersey, it doesn't. But this works until resolution of
         * the issue:
         */
        if (resolver == null) {
            resolver = _providers.getContextResolver(ObjectMapper.class, null);
        }
        if (resolver != null) {
            return resolver.getContext(type);
        }
    }
    return null;
}

Mais au moins, je ne peux pas accéder à https://jersey.dev.Java.net/issues/show_bug.cgi?id=288 , donc je ne sais pas de quoi parle ce bogue.

Mais j'ai trouvé une solution de contournement (un hack si vous voulez). Il suffit d'étendre JacksonJsonProvider avec l'annotation appropriée et de renvoyer votre ObjectMapper comme ceci:

@Provider
@Consumes(MediaType.APPLICATION_JSON) // NOTE: required to support "non-standard" JSON variants
@Produces(MediaType.APPLICATION_JSON)
public class JacksonHackProvider extends JacksonJsonProvider {
    @Override
    protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
        return new MyCustomObjectMapper();
    }
}

Pas besoin de faire quoi que ce soit, il s'enregistrera lui-même (vérifiez avec le journal, il s'enregistrera la première fois que vous accédez à un service de repos json). Cela fonctionne maintenant pour moi, pas élégant, mais j'ai abandonné.

EDIT: à utiliser avec prudence - Je rencontre un bogue peut-être lié à ce piratage: Android volley ne peut pas envoyer de demande POST/PUT avec un corps de demande, obtenant toujours 400 du cadre, je vais enquêter et rendre compte de mes conclusions.

EDIT2: Ce hack était en effet responsable d'un 400 générique chaque fois qu'une Android avec volley et OKHTTP client essayait de faire un POST ou PUT demande donc ne l'utilisez pas - dans mon maillot de test 2.6 semble résoudre ce problème afin que vous puissiez utiliser @Provide approche

11
patrickf

Malheureusement, tout le monde rend cela beaucoup plus difficile que nécessaire. L'équipe de Jersey, dans sa sagesse, a décidé d'intégrer Jackson 1.9, donc ses trucs ne vous aideront pas.

Mais c'était assez facile pour moi. Faites ceci:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.3.0</version>
    </dependency>

Maintenant, débarrassez-vous de ceci:

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

Puis dans votre web.xml changez cette ligne:

<param-value>ie.cit.nimbus.sample</param-value>

Être:

<param-value>ie.cit.nimbus.sample,com.fasterxml.jackson.jaxrs.json</param-value>

Ça devrait le faire.

8
robert_difalco

En utilisant Jersey 2.13, vous pouvez forcer le @Provider pour utiliser le même ObjectMapper ne doit en créer qu'un ObjectMapper:

package com.example.api.environment.configs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

  private final ObjectMapper objectMapper;

  public JacksonConfig() {
    objectMapper = new ObjectMapper()
          .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
          .registerModule(new JodaModule());
  };

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return objectMapper;
  }
}

J'utilise ceci pour @Autowire dans mon ObjectMapper pour générer json-schema documents à la volée.

5
Nthalk

Il existe de nombreuses façons d'intégrer jackson avec Jax-rs Implémentation de Jersey.

Si vous jetez un œil au tutoriel Mkyong: http://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/ Il semble que vous devriez également passer le "POJOMappingFeature" -> true dans les paramètres init du web.xml. Je pense que cela fonctionne pour Jersey 1.8

Si vous regardez plutôt la documentation officielle de Jersey: https://jersey.Java.net/nonav/documentation/latest/user-guide.html#json.jackson Il semble que vous devriez implémenter un Fournisseur Jax-rs et ajoutez ce fournisseur à vos ressources d'application

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>

Ils vous fournissent un exemple de la façon de procéder https://github.com/jersey/jersey/blob/2.4.1/examples/json-jackson/src/main/Java/org/glassfish/jersey/ examples/jackson/MyObjectMapperProvider.Java

J'ai utilisé cette méthode et cela a résolu mes problèmes, et les annotations Jackson sont correctement analysées par le fournisseur Jackson.


Hors sujet je vous suggère d'utiliser cette syntaxe dans votre bean pour initialiser la map:

import static Java.util.Arrays.asList;

public static class Foobar {
        @JsonIgnore
        private String foo = "foo";
        private String baa = "baa";
        private Map<String, List<? extends Number>> map = new HashMap<>(){{
            put("even", asList(2, 4, 6, 8, 10));
            put("odd", asList(1, 3, 5, 7, 9));
            put("float", asList(1.1F, 2.2F, 3.3F));
        }};

        public Foobar() {
        }

        public String getFoo() {
            return foo;
        }

        public void setFoo(String foo) {
            this.foo = foo;
        }

        public String getBaa() {
            return baa;
        }

        public void setBaa(String baa) {
            this.baa = baa;
        }

        @JsonAnyGetter
        public Map<String, List<? extends Number>> getMap() {
            return map;
        }

        public void setMap(Map<String, List<? extends Number>> map) {
            this.map = map;
        }
    }
1
thermz