JAX-RS Publicar múltiples objetos

Tengo un método;

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

Ahora sé que puedo publicar un solo objeto en formato json, simplemente ponerlo en el cuerpo. ¿Pero es posible hacer múltiples objetos? ¿Si es así, cómo?

La respuesta es no .

El motivo es simple: esto acerca de los parámetros que puede recibir en un método. Deben estar relacionados con la solicitud. ¿Derecha? Por lo tanto, deben ser encabezados o cookies o parámetros de consulta o parámetros de matriz o parámetros de ruta o cuerpo de solicitud . (Solo para contar la historia completa hay tipos adicionales de parámetros llamados contexto).

Ahora, cuando recibe un objeto JSON en su solicitud, lo recibe en un cuerpo de solicitud . ¿Cuántos cuerpos puede tener la solicitud? Uno y solo uno. Entonces puedes recibir solo un objeto JSON.

No puede usar su método de esta manera como lo indica Tarlog correctamente.

Sin embargo, puedes hacer esto:

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

o esto:

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

Además, siempre puedes combinar tu método con los parámetros GET:

 @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(List objects, @QueryParam("objectTwoId") long objectTwoId) 

Si miramos lo que el OP intenta hacer, él / ella está tratando de publicar dos objetos JSON (posiblemente no relacionados). En primer lugar, cualquier solución para intentar enviar una parte como el cuerpo, y una parte como otro param, IMO, son soluciones horribles . Los datos de POST deben ir en el cuerpo. No es correcto hacer algo solo porque funciona. Algunas soluciones pueden estar violando los principios básicos de REST.

Veo algunas soluciones

  1. Utilice application / x-www-form-urlencoded
  2. Use Multipart
  3. Solo envuélvalos en un solo objeto padre

1. Utilice application / x-www-form-urlencoded

Otra opción es simplemente usar application/x-www-form-urlencoded . De hecho, podemos tener valores JSON. Por ejemplo

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

Lo único que necesitamos para que esto funcione es un ParamConverterProvider para que esto funcione. A continuación hay uno que ha sido implementado por Michal Gadjos del equipo de Jersey (se encuentra aquí con una explicación ).

 @Provider public class JacksonJsonParamConverterProvider implements ParamConverterProvider { @Context private Providers providers; @Override public  ParamConverter getConverter(final Class rawType, final Type genericType, final Annotation[] annotations) { // Check whether we can convert the given type with Jackson. final MessageBodyReader 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 contextResolver = providers .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE); final ObjectMapper mapper = contextResolver != null ? contextResolver.getContext(rawType) : new ObjectMapper(); // Create ParamConverter. return new ParamConverter() { @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 no está buscando recursos y proveedores, solo registre este proveedor, y el ejemplo anterior debería funcionar.

2. Use Multipart

Una solución que nadie ha mencionado, es usar multiparte . Esto nos permite enviar partes arbitrarias en una solicitud. Dado que cada solicitud solo puede tener un cuerpo de entidad, multipart es la solución alternativa, ya que permite tener diferentes partes (con sus propios tipos de contenido) como parte del cuerpo de la entidad.

Aquí hay un ejemplo con Jersey (ver el documento oficial aquí )

Dependencia

  org.glassfish.jersey.media jersey-media-multipart ${jersey-2.x.version}  

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

Clase de recursos

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

Ahora la parte difícil con algunos clientes es que no hay una manera de establecer el Content-Type de Content-Type de cada parte del cuerpo, que es necesario para que lo anterior funcione. El proveedor de varias partes buscará el lector del cuerpo del mensaje, según el tipo de cada parte. Si no está configurado para application/json o un tipo, el Foo o Bar tiene un lector para, esto fallará. Usaremos JSON aquí. No hay configuración adicional, sino tener un lector disponible. Usaré Jackson. Con la dependencia a continuación, no se requiere ninguna otra configuración, ya que el proveedor se descubrirá a través del escaneo de classpath.

  org.glassfish.jersey.media jersey-media-json-jackson ${jersey-2.x.version}  

Ahora la prueba. Estaré usando cURL . Puedes ver que establecí explícitamente el tipo de Content-Type para cada parte con el type . El -F significa una parte diferente. (Consulte la parte inferior de la publicación para obtener una idea de cómo se ve realmente el cuerpo de la solicitud).

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
Resultado: FooFoo; BarBar FooFoo; BarBar

El resultado es exactamente como esperábamos. Si nos fijamos en el método de recursos, todo lo que hacemos es devolver esta cadena foo.foo + "; " + bar.bar , recostackda de los dos objetos JSON.

Puede ver algunos ejemplos usando algunos clientes JAX-RS diferentes, en los enlaces a continuación. También verá un ejemplo del lado del servidor también de esas diferentes implementaciones de JAX-RS. Cada enlace debe tener en algún lugar un enlace a la documentación oficial para esa implementación

  • Ejemplo de Jersey
  • Ejemplo Resteasy
  • Ejemplo de CXF

Existen otras implementaciones de JAX-RS, pero deberá encontrar la documentación para usted. Los tres anteriores son los únicos con los que tengo experiencia.

En cuanto a los clientes de Javascript, la mayoría del ejemplo que veo (por ejemplo, algunos de ellos implican establecer el Content-Type en indefinido / falso (utilizando FormData ), dejando que el navegador lo maneje. Pero esto no nos funcionará, ya que el navegador no configurará el tipo de Content-Type para cada parte. Y el tipo predeterminado es text/plain .

Estoy seguro de que hay bibliotecas que permiten establecer el tipo para cada parte, pero para mostrarle cómo se puede hacer manualmente, publicaré un ejemplo (obtuve un poco de ayuda desde aquí . Voy a utilizar Angular , pero el arduo trabajo de construir el cuerpo de la entidad será simple y antiguo Javascript.

 < !DOCTYPE html>       

{{result}}

La parte interesante es la función createRequest . Aquí es donde creamos la multiparte, estableciendo el Content-Type de Content-Type de cada parte en application/json , y concatenando los objetos foo y bar stringificados a cada parte. Si no está familiarizado con el formato de varias partes, consulte aquí para obtener más información . La otra parte interesante es el encabezado. Lo configuramos en multipart/form-data .

A continuación está el resultado. En Angular acabo de utilizar el resultado para mostrar en el HTML, con $scope.result = response.data . El botón que ves es solo para hacer la solicitud. También verá los datos de solicitud en firebug

enter image description here

3. Solo envuélvalos en un solo objeto padre

Esta opción debe ser autoexplicativa, como otros ya han mencionado.

El siguiente enfoque generalmente se aplica en este tipo de casos:

 TransferObject { ObjectOne objectOne; ObjectTwo objectTwo; //getters/setters } @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(TransferObject object){ // object.getObejctOne().... } 

No puede poner dos objetos separados en una sola llamada POST como lo explica Tarlog.

De todos modos, podría crear un tercer objeto contenedor que contenga los dos primeros objetos y pasarlo dentro de la llamada POS.

También me he enfrentado a estos problemas. Tal vez esto ayude.

 @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 mapper = new HashMap<>(); //Diamond ))) mapper = (HashMap) 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 

Se puede hacer teniendo el método POST declarado para aceptar una matriz de objetos. Ejemplo como este

 T[] create(@RequestBody T[] objects) { for( T object : objects ) { service.create(object); } }