JAX-RS Post multiple objects

j'ai une méthode;

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(ObjectOne objectOne, ObjectTwo objectTwo)

maintenant je sais que je peux poster un seul objet dans le format json, juste le mettre dans le corps. Mais est-il possible de faire plusieurs objets? Si oui, comment?

62
demandé sur Thys 2011-04-05 18:09:36

7 réponses

la réponse est Non .

la raison est simple: il s'agit des paramètres que vous pouvez recevoir dans une méthode. Ils doivent être liés à la demande. Droit? Ils doivent donc être des en-têtes ou des cookies ou des paramètres de requête ou des paramètres de matrice ou des paramètres de chemin ou request body . (Juste pour raconter l'histoire complète il y a d'autres types de paramètres appelés contexte).

maintenant, quand vous recevez Objet JSON dans votre requête, vous le recevez dans un request body . Combien de corps la demande peut-elle avoir? Un et un seul. Vous ne pouvez donc recevoir qu'un seul objet JSON.

57
répondu Tarlog 2011-04-06 10:43:02

vous ne peut pas utilisez votre méthode comme cela correctement indiqué par Tarlog.

Cependant, vous pouvez faire ceci:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects)

ou ceci:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(BeanWithObjectOneAndObjectTwo containerObject)

de plus, vous pouvez toujours combiner votre méthode avec les paramètres GET:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId)
77
répondu tine2k 2015-09-17 10:11:48

si nous regardons ce que L'OP essaie de faire, il/elle essaie de poster deux objets JSON (peut-être sans rapport). Tout d'abord toute solution pour essayer d'envoyer une partie comme le corps, et une partie comme un autre param, IMO, sont horrible solutions. Les données POST devraient aller dans le corps. Ce n'est pas bien de faire quelque chose juste parce que ça marche. Certaines solutions de rechange pourraient violer les principes de repos de base.

je vois quelques solutions

  1. Use application / x-www-form-urlencoded
  2. Utilisation Multipart
  3. enveloppez-les dans un seul objet parent

1. L'utilisation de l'application/x-www-form-urlencoded

une autre option est d'utiliser simplement application/x-www-form-urlencoded . On peut avoir des valeurs JSON. Par exemple 1519340920"

curl -v http://localhost:8080/api/model \
     -d 'one={"modelOne":"helloone"}' \
     -d 'two={"modelTwo":"hellotwo"}'

public class ModelOne {
    public String modelOne;
}

public class ModelTwo {
    public String modelTwo;
}

@Path("model")
public class ModelResource {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String post(@FormParam("one") ModelOne modelOne,
                       @FormParam("two") ModelTwo modelTwo) {
        return modelOne.modelOne + ":" + modelTwo.modelTwo;
    }
}

la seule chose dont nous avons besoin pour que cela fonctionne est un ParamConverterProvider pour que cela fonctionne. Dessous est celui qui a été mis en œuvre par Michal Gadjos de L'équipe de Jersey (trouvé ici avec une explication ).

@Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {

    @Context
    private Providers providers;

    @Override
    public <T> ParamConverter<T> getConverter(final Class<T> rawType,
                                              final Type genericType,
                                              final Annotation[] annotations) {
        // Check whether we can convert the given type with Jackson.
        final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
                genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
        if (mbr == null
              || !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
            return null;
        }

        // Obtain custom ObjectMapper for special handling.
        final ContextResolver<ObjectMapper> contextResolver = providers
                .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);

        final ObjectMapper mapper = contextResolver != null ?
                contextResolver.getContext(rawType) : new ObjectMapper();

        // Create ParamConverter.
        return new ParamConverter<T>() {

            @Override
            public T fromString(final String value) {
                try {
                    return mapper.reader(rawType).readValue(value);
                } catch (IOException e) {
                    throw new ProcessingException(e);
                }
            }

            @Override
            public String toString(final T value) {
                try {
                    return mapper.writer().writeValueAsString(value);
                } catch (JsonProcessingException e) {
                    throw new ProcessingException(e);
                }
            }
        };
    }
}

si vous n'analysez pas les ressources et les fournisseurs, Inscrivez ce fournisseur, et l'exemple ci-dessus devrait fonctionner.

2. Utilisation Multipart

une solution que personne n'a mentionnée, est d'utiliser multipart . Cela nous permet d'envoyer des parties arbitraires dans un demande. Puisque chaque requête ne peut avoir qu'un seul corps d'entity, multipart est le travail autour, car il permet d'avoir différentes parties (avec leurs propres types de contenu) en tant que partie du corps d'entity.

voici un exemple d'utilisation de Jersey (voir Doc officiel ici )

dépendance

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>

Enregistrer le MultipartFeature

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {
        packages("stackoverflow.jersey");
        register(MultiPartFeature.class);
    }
}

classe de Ressource

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;

@Path("foobar")
public class MultipartResource {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response postFooBar(@FormDataParam("foo") Foo foo,
                               @FormDataParam("bar") Bar bar) {
        String response = foo.foo + "; " + bar.bar;
        return Response.ok(response).build();
    }

    public static class Foo {
        public String foo;
    }

    public static class Bar {
        public String bar;
    }
}

maintenant la partie délicate avec certains clients est qu'il n'y a pas un moyen de régler le Content-Type de chaque partie du corps, qui est nécessaire pour le ci-dessus pour fonctionner. Le fournisseur multipart cherchera le lecteur de corps de message, basé sur le type de chaque partie. Si elle n'est pas définie à application/json ou un type, le Foo ou Bar a un lecteur pour, cela échouera. Nous utiliserons JSON ici. Il n'y a pas de configuration supplémentaire, mais un lecteur est disponible. Je vais utiliser Jackson. Avec la dépendance ci-dessous, aucune autre configuration ne devrait être requise, car le fournisseur sera découvert par classpath scanning.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>

maintenant le test. Je serai à l'aide de cURL . Vous pouvez voir que j'ai explicitement défini le Content-Type pour chaque partie avec type . Le -F signifie à la partie différente. (Voir tout en bas du post pour une idée de l'apparence du corps de la requête.)

curl -v -X POST \ -H "Content-Type:multipart/form-data" \ -F "bar={\"bar\":\"BarBar\"};type=application/json" \ -F "foo={\"foo\":\"FooFoo\"};type=application/json" \ http://localhost:8080/api/foobar

résultat: FooFoo; BarBar

Le résultat est exactement comme nous l'espérions. Si vous regardez la méthode resource, tout ce que nous faisons est de retourner cette chaîne foo.foo + "; " + bar.bar , recueillie à partir des deux objets JSON.

vous pouvez voir quelques exemples en utilisant des clients JAX-RS différents, dans les liens ci-dessous. Vous verrez aussi quelques exemples côté serveur provenant de ces différents JAX-RS application. Chaque lien doit comporter quelque part un lien vers la documentation officielle pour cette mise en œuvre

il existe d'autres implémentations JAX-RS, mais vous devrez trouver vous-même la documentation nécessaire. Les trois ci-dessus sont les seuls que j'ai de l'expérience avec.

en ce qui concerne les clients Javascript, la plupart de l'exemple que je vois (par exemple certains de ces impliquent de définir le Content-Type à non défini/false (en utilisant FormData ), laissant le navigateur gérer l'it. Mais cela ne fonctionnera pas pour nous, car le Navigateur ne sera pas mis le Content-Type pour chaque partie. Et le type par défaut est text/plain .

je suis sûr qu'il y a des bibliothèques qui permettent de définir le type de chaque partie, mais juste pour vous montrer comment il peut être fait manuellement, je vais poster un exemple (un peu d'aide de ici . Je vais utiliser L'angulaire, mais le travail grognon de construire le corps de l'entité sera simple Vieux Javascript.

<!DOCTYPE html>
<html ng-app="multipartApp">
    <head>
        <script src="js/libs/angular.js/angular.js"></script>
        <script>
            angular.module("multipartApp", [])
            .controller("defaultCtrl", function($scope, $http) {

                $scope.sendData = function() {
                    var foo = JSON.stringify({foo: "FooFoo"});
                    var bar = JSON.stringify({bar: "BarBar"});

                    var boundary = Math.random().toString().substr(2);                    
                    var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;

                    $http({
                        url: "/api/foobar",
                        headers: { "Content-Type": header }, 
                        data: createRequest(foo, bar, boundary),
                        method: "POST"
                    }).then(function(response) {
                        $scope.result = response.data;
                    });
                };

                function createRequest(foo, bar, boundary) {
                    var multipart = "";
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=foo"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + foo + "\r\n";        
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=bar"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + bar + "\r\n";
                    multipart += "--" + boundary + "--\r\n";
                    return multipart;
                }
            });
        </script>
    </head>
    <body>
        <div ng-controller="defaultCtrl">
            <button ng-click="sendData()">Send</button>
            <p>{{result}}</p>
        </div>
    </body>
</html>

la partie intéressante est la fonction createRequest . C'est ici que nous construisons le multipart, réglant le Content-Type de chaque pièce sur application/json , et concaténant le stringified foo et bar des objets à chaque partie. Si vous n'êtes pas familier multipart format voir ici pour plus d'informations . L'autre partie intéressante est l'en-tête. On a mis multipart/form-data .

ci-Dessous est le résultat. En angle, je viens d'utiliser le résultat pour afficher en HTML, avec $scope.result = response.data . Le bouton que vous voyez était juste pour faire la demande. Vous verrez également les données de la demande dans firebug

enter image description here

3. Il suffit de les envelopper dans un seul objet parent

Cette option doit être explicite, comme d'autres l'ont déjà mentionné.

29
répondu Paul Samsotha 2017-05-23 11:54:43

l'approche suivante est habituellement appliquée dans ce genre de cas:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
//        object.getObejctOne()....
}
6
répondu Oleksii Kyslytsyn 2015-03-01 19:28:53

vous ne pouvez pas mettre deux objets séparés dans un seul appel POST comme expliqué par Tarlog.

de toute façon, vous pouvez créer un troisième objet conteneur qui contient les deux premiers objets et passer celui-ci dans l'appel POS.

4
répondu Giorgio 2012-07-16 08:52:40

j'ai également fait face à ces problèmes. Peut-être que cela aidera.

@POST
@Path("/{par}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Object centralService(@PathParam("par") String operation, Object requestEntity) throws JSONException {

    ObjectMapper objectMapper=new ObjectMapper();

    Cars cars = new Cars();  
    Seller seller = new Seller();
    String someThingElse;

    HashMap<String, Object> mapper = new HashMap<>(); //Diamond )))

    mapper = (HashMap<String, Object>) requestEntity;

    cars=objectMapper.convertValue(mapper.get("cars"), Cars.class);
    seller=objectMapper.convertValue(mapper.get("seller"), Seller.class);
    someThingElse=objectMapper.convertValue(mapper.get("someThingElse"), String.class);

    System.out.println("Cars Data "+cars.toString());

    System.out.println("Sellers Data "+seller.toString());

    System.out.println("SomeThingElse "+someThingElse);

    if (operation.equals("search")) {
        System.out.println("Searching");
    } else if (operation.equals("insertNewData")) {
        System.out.println("Inserting New Data");
    } else if (operation.equals("buyCar")) {
        System.out.println("Buying new Car");
    }

    JSONObject json=new JSONObject();
    json.put("result","Works Fine!!!");


    return json.toString();

}


*******CARS POJO********@XmlRootElement for Mapping Object to XML or JSON***

@XmlRootElement
public class Cars {
    private int id;
    private String brand;
    private String model;
    private String body_type;
    private String fuel;
    private String engine_volume;
    private String horsepower;
    private String transmission;
    private String drive;
    private String status;
    private String mileage;
    private String price;
    private String description;
    private String picture;
    private String fk_seller_oid;
    } // Setters and Getters Omitted 

*******SELLER POJO********@XmlRootElement for Mapping Object to XML or JSON***

@XmlRootElement
public class Seller {
    private int id;
    private String name;
    private String surname;
    private String phone;
    private String email;
    private String country;
    private String city;
    private String paste_date;
    }//Setters and Getters omitted too


*********************FRONT END Looks Like This******************

$(function(){
$('#post').on('click',function(){
        console.log('Begins');
        $.ajax({
            type:'POST',
            url: '/ENGINE/cars/test',
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data:complexObject(),
            success: function(data){
                console.log('Sended and returned'+JSON.stringify(data));
            },
            error: function(err){
                console.log('Error');
                console.log("AJAX error in request: " + JSON.stringify(err, null, 2));
            }
        }); //-- END of Ajax
        console.log('Ends POST');
        console.log(formToJSON());

    }); // -- END of click function   POST


function complexObject(){
    return JSON.stringify({
                "cars":{"id":"1234","brand":"Mercedes","model":"S class","body_type":"Sedan","fuel":"Benzoline","engine_volume":"6.5",
                "horsepower":"1600","transmission":"Automat","drive":"Full PLag","status":"new","mileage":"7.00","price":"15000",
                "description":"new car and very nice car","picture":"mercedes.jpg","fk_seller_oid":"1234444"},
        "seller":{  "id":"234","name":"Djonotan","surname":"Klinton","phone":"+994707702747","email":"email@gmail.com",                 "country":"Azeribaijan","city":"Baku","paste_date":"20150327"},
        "someThingElse":"String type of element"        
    }); 
} //-- END of Complex Object
});// -- END of JQuery -  Ajax
1
répondu Musa 2016-03-17 00:21:16

cela peut être fait en faisant déclarer la méthode POST pour accepter le tableau d'objets. Exemple comme ceci

T[] create(@RequestBody T[] objects) {
for( T object : objects ) {
   service.create(object);
  }
}
0
répondu Yoga Gowda 2016-11-09 19:05:35