web-dev-qa-db-fra.com

Comment renvoyer une réponse JSON partielle en utilisant Java?

Je construis une API RESTful et je veux offrir aux développeurs la possibilité de choisir les champs à retourner dans la réponse JSON. Ce billet de blog montre des exemples de la façon dont plusieurs API (Google, Facebook, LinkedIn) permettent aux développeurs de personnaliser la réponse. C'est ce qu'on appelle une réponse partielle.

Un exemple pourrait ressembler à ceci:

/users/123?fields=userId,fullname,title

Dans l'exemple ci-dessus, l'API doit renvoyer les champs userId, fullName et title pour l'utilisateur "123".

Je cherche des idées sur la façon de mettre en œuvre cela dans mon service Web RESTful. J'utilise actuellement CXF (edit: et Jackson) mais je suis prêt à essayer une autre implémentation JAX-RS.

Voici ce que j'ai actuellement. Il renvoie un objet utilisateur complet. Comment puis-je renvoyer uniquement les champs que l'appelant API souhaite à l'exécution en fonction du paramètre "champs"? Je ne veux pas rendre les autres champs Null. Je ne veux tout simplement pas les retourner.

@GET
@Path("/{userId}")
@Produces("application/json")
public User getUser(@PathParam("userId") Long userId, 
    @DefaultValue("userId,fullname,title") @QueryParam("fields") String fields) {

User user = userService.findOne(userId);

StringTokenizer st = new StringTokenizer(fields, ",");
while (st.hasMoreTokens()) {

    // here's where i would like to select only the fields i want to return

}
return user;
}

MISE À JOUR:

J'ai suivi le lien de unludo qui a ensuite lié à ceci: http://wiki.fasterxml.com/JacksonFeatureJsonFilter

Avec cette information, j'ai ajouté @JsonFilter("myFilter") à ma classe de domaine. J'ai ensuite modifié ma méthode de service RESTful pour renvoyer String au lieu de User comme suit:

@GET
@Path("/{userId}")
@Produces("application/json")
public String getUser(@PathParam("userId") Long userId,
                    @DefaultValue("userId,fullname,title") @QueryParam("fields") String fields) {

    User user = userService.findOne(userId);

    StringTokenizer st = new StringTokenizer(fields, ",");
    Set<String> filterProperties = new HashSet<String>();
    while (st.hasMoreTokens()) {
        filterProperties.add(st.nextToken());
    }

    ObjectMapper mapper = new ObjectMapper();
    FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));

    try {
        String json = mapper.filteredWriter(filters).writeValueAsString(user);
        return json;
    } catch (IOException e) {
        e.printStackTrace();
    return e.getMessage();
    }
}

J'ai besoin de faire plus de tests mais jusqu'ici tout va bien.

41
Justin

Si vous utilisez Jackson (une excellente bibliothèque JSON - une sorte de standard pour Java je crois), vous pouvez utiliser le @View annotation pour filtrer ce que vous voulez dans l'objet résultant.

Je comprends que vous voulez quelque chose de dynamique, donc c'est un peu plus compliqué. Vous trouverez ce que vous cherchez ici: http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html (regardez 6. Filtrage entièrement dynamique: @JsonFilter).

Je serais intéressé par la solution que vous trouverez.

18
unludo

La création d'une instance ObjectMapper à l'intérieur de la méthode de ressource pour chaque demande peut entraîner une surcharge de performances importante. Selon les meilleures pratiques de performance Jackson les mappeurs d'objets sont coûteuses à créer.

Au lieu de cela, vous pouvez personnaliser l'écrivain d'objets Jackson du fournisseur JAX-RS dans la méthode de ressource en utilisant la fonction Jackson 2.3 ObjectWriterModifier/ObjectReaderModifier .

Voici un exemple qui montre comment enregistrer un objet local de thread ObjectWriterModifier qui modifie l'ensemble des filtres appliqués pour le fournisseur JAX-RS Jackson utilisé dans une méthode de ressource. Notez que je n'ai pas testé le code par rapport à une implémentation JAX-RS.

public class JacksonObjectWriterModifier2 {

    private static class FilterModifier extends ObjectWriterModifier {
        private final FilterProvider provider;

        private FilterModifier(FilterProvider provider) {
            this.provider = provider;
        }

        @Override
        public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> responseHeaders,
                                   Object valueToWrite, ObjectWriter w, JsonGenerator g) throws IOException {
            return w.with(provider);
        }
    }

    @JsonFilter("filter1")
    public static class Bean {
        public final String field1;
        public final String field2;

        public Bean(String field1, String field2) {
            this.field1 = field1;
            this.field2 = field2;
        }
    }

    public static void main(String[] args) throws IOException {
        Bean b = new Bean("a", "b");
        JacksonJsonProvider provider = new JacksonJsonProvider();
        ObjectWriterInjector.set(new FilterModifier(new SimpleFilterProvider().addFilter("filter1",
                SimpleBeanPropertyFilter.filterOutAllExcept("field1"))));

        provider.writeTo(b, Bean.class, null, null, MediaType.APPLICATION_JSON_TYPE, null, System.out);
    }

}

Production:

{"field1":"a"}
5
Alexey Gavrilov

La bibliothèque jersey-entity-filtering peut le faire:

https://github.com/jersey/jersey/tree/2.22.2/examples/entity-filtering-selectable

https://jersey.Java.net/documentation/latest/entity-filtering.html

Exemple:

Mon objet

public class Address {

    private String streetAddress;
    private String region;
    private PhoneNumber phoneNumber;
}

URL

people/1234? select = streetAddress, région

REVENIR

{
   "streetAddress": "2 square Tyson",
   "region": "Texas"
}

Ajouter à Maven

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-entity-filtering</artifactId>
    <version>2.22.2</version>
</dependency>
4
Hydral