So I am trying to build a simple REST API and wanted to try out spark but for some reason I can't seem to extract any parameters.
Here is my endpoint for testing this:
post("/hello", (req, res) -> {
String str = req.attribute(":username"); //TODO THIS IS ALWAYS NULL!!!!!!!
System.out.println(str);
System.out.println("BODY IS WORKING:");
System.out.println(req.body().toString());
return "PANNKAKA";
});
Now if I try to make a request at http://localhost:4567/hello with the body {
"username": "bla"
}, the str variable is just null. But if I call on the body method on req, req.body().toString(); it does indeed get the body: {
"username": "bla"
} printed to the console. So the body is coming through.
This is the result in the console window:
null
BODY IS WORKING:
[
{
"username": "bla"
}
]
So how do you extract the parameter from the request's body? I have tried lots of different formatting on the param name but it just doesn't work. Been sitting with this for hours now!
I have looked at the documentation and I believe I do the correct thing:
http://sparkjava.com/documentation.html
I guess there are few ways to do this. Also, you didn't mention it by I'm assuming you send your data formatted as JSON.
I do that using an ObjectMapper (using this package com.fasterxml.jackson.databind.ObjectMapper) and a PayLoad class.
I create a simple class used as the PayLoad. It contains just fields and their getters and setters. for each value you send in the JSON you'll create the corresponding field, with exactly the same name In your case you'll have a field called username), and then the object mapper maps the JSON that you sent from the client to this class pattern. Example:
public class UserPayload {
private long id;
private String username;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Then I parse the request body into this payload using the object mapper:
class SomeClass {
...
private static Object postUser(Request req, Response res) throws JSONException {
ObjectMapper mapper = new ObjectMapper();
UserPayload pl = null;
try {
pl = mapper.readValue(req.body(), UserPayload.class); // <------
} catch (JsonMappingException e1) {
...
} catch (Exception e2) {
...
}
System.out.println(pl.getUsername() + " " + pl.getId());
...
}
}
And of course register your route:
post("/hello", SomeClass::postUser);
Hope it helps.
If someone uses a simpler way, I'll be happy to hear.
You should have declared your route like the following, with the parameter :username as part of the route name:
post("/hello/:username", (req, res) -> {
String str = req.attribute(":username"); //TODO THIS IS ALWAYS NULL!!!!!!!
System.out.println(str);
System.out.println("BODY IS WORKING:");
System.out.println(req.body().toString());
return "PANNKAKA";
});
or, if what you wanted is to deal with query parameters, just take a look at this answer.
as Laercio said, if you want to get the value of this line
String str = req.attribute(":username"); //TODO THIS IS ALWAYS NULL!!!!!!!
you should declare the same var as part of your URI
post("/hello/:username" ...) ...
but if you are trying to send a JSON as Content-Type: application/json you won't get the params by that way, this only works if your send the value as Content-Type: application/x-www-form-urlencoded and then your can use req.params("username") this last is the way that ordinary HTML form send the data
Java doesn't handle very well JSON by default, you need to do some tricks to do that, or even better use Gson
If you don't want to use Gson, so you need to read the body line per line and handle by your own that data sent by application/json to get the data.
Related
I want to use OKHttp3-based RestTemplate to remotely call the interface queryByIds to get basic user information.
#Configuration
public class CloudConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
the implementing method queryByIds is below:
#GetMapping("/queryByIds")
public GraceJSONResult queryByIds(#RequestParam String userIds) {
if (StringUtils.isBlank(userIds)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);
ArrayList<AppUserVO> userVOList = new ArrayList<>();
assert userIdList != null;
userIdList.forEach(id -> {
AppUserVO userInfo = getBasicUserInfo(id);
userVOList.add(userInfo);
});
return GraceJSONResult.ok(userVOList);
}
Here is the bussiness code, the http://user.mootalk.com is switched to localhost using SwitchHosts:
// Get the basic information of each user and put it in userVOList
String userServerUrlExecute = "http://user.mootalk.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(publisherIdSet);
System.out.println(userServerUrlExecute);
// the debugger paused
ResponseEntity<GraceJSONResult> entity =
restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
Here is my util class:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
/**
* json converter
*/
public class JsonUtils {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
When I debug the code ,it didn't go into the queryByIds method, and the console printed the userServerUrlExecute below:
But if you construct a request in Postman like this:
http://user.mootalk.com:8003/user/queryByIds?userIds=210726G71HSY2YY8, it could go into the queryByIds method but the userIdList turned out to be null.
If you construct a request in Postman like this :
http://user.mootalk.com:8003/user/queryByIds?userIds=[\"210726G71HSY2YY8\",\"200628AFYM7AGWPH\"],it works well.
So what's wrong with my code while passing the param?
Later message1:
Now the last construct request did't go into queryByIds both in Chrome's address bar and Postman, it threw a 400 Bad Request;
I replaced #RequestParam with #RequestBody in queryByIds, it still threw a 400 Bad Request
Latter message2:
Now it works...with the same code. This is really a mystery.
Really I wouldn't just pass in JSON into a query parameter like you are doing, it's nasty and doesn't really follow any sort of RESTFUL API standard.
You have 4 options as I see it:
Change to a HTTP POST request and pass in the JSON Data into the request body (Recommend and follows best practise).
If for whatever reasons your requirements are it needs to be a HTTP GET request and needs to be a query parameter then you need to base64 encode the JSON before passing it in.
?userIds=W1wiMjEwNzI2RzcxSFNZMllZOFwiLFwiMjAwNjI4QUZZTTdBR1dQSFwiXQ==
Again if it has to be a HTTP GET request but it doesn't need to be JSON then I would do a comma separated list of IDs into the query parameter
?userIds=210726G71HSY2YY8,200628AFYM7AGWPH
Just request 1 user id at a time.
Your requirements are probably going to be what determines the approach you take.
I'm presently migrating from the Java ASK-SDK v1 to Java ASK SDK v2.
I'm trying to return a webhook call using the ResponseBuilder class that I built my response up and the data is correct, however when I try to populate the HTTP body with the JSON text, the ResponseBuilder.toString() value doesn't just populate the data with just the string, I get the following:
Optional[class Response {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>Some of the things you can say are What would you like to do?</speak>
}
card: null
reprompt: class Reprompt {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>You can say ..., is that what you want?</speak>
}
}
directives: []
shouldEndSession: false
canFulfillIntent: null
}]
Is there another way to get the string for the body of the response? The BaseSkillResponse has a getResponse() call, however, I cannot figure out how to use the class to generate the String response output.
I was able to get the string with the following in my class:
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
myFunction(){
try{
return toJsonString(responseBuilder.build().get());
} catch(IOException e) {
e.printStackTrace();
}
}
public String toJsonString(Response response)throws IOException {
return OBJECT_MAPPER.writeValueAsString(response);
}
Solve this by doing the following:
public String toJsonString(Response response)throws IOException
{
JacksonSerializer jacksonSerializer = new JacksonSerializer();
constructedResponse = jacksonSerializer.serialize(response);
JSONObject jsonObject = new JSONObject();
jsonObject.put("response",constructedResponse);
}
Is there a way I can simplify this:
#PostMapping(value = "create", consumes = "application/json", produces = "application/json")
public Response create(#RequestBody ObjectNode json) {
return new Response(json.get("name").asText(), 200);
}
Mainly I wonder if it's possible to annotate consumes & produces. My app will be an API service so all requests/responses will be JSON based. I don't want to keep that over each controller method.
Less important:
I know I can pass #RequestParam Comment comment if this is a method to create a comment but what if I want to create a comment and something else at the same time from the same method.
Is there a cleaner way than doing ObjectNode and json and than json.get().as...
As it turns out you can annotate your methods/controllers with #ResponseBody and #RequestBody to achieve the same result.
MyServer.class
#POST
public Response save(String data) {
return Response.status(Response.Status.ACCEPTED).entity(repository.save(data)).build();
}
Now it will go to the server as post request.
if no id present, so add this code.
ResourceConverter converter = new ...
converter.disableDeserialisationOption(DeserializationFeature.REQUIRE_RESOURCE_ID);
This allows you to remove id restriction.
Alternative is that you should use current snapshot version
Here is the save method from the repository class
public String save(String data) {
Server myServer= converter.readObject(data.getBytes(), Server.class);
Key<Server> savedMyServer = datastore.save(myServer);
Server usingKey = datastore.getByKey(Server.class, savedMyServer);
try {
return new String(converter.writeObject(usingKey), StandardCharsets.UTF_8);
} catch (JsonProcessingException | IllegalAccessException e) {
LOGGER.debug(e.getMessage());
}
return null;
}
I am getting this response from the server {"status":"true","msg":"success"}
I am trying to parse this json string using Jackson parser library but somehow I am facing mapping-exception stating
com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
at [Source: java.io.StringReader#421ea4c0; line: 1, column: 1]
Why do we get this kind of exceptions?
How to understand what is causing this exception?
I am trying to parse using following way:
StatusResponses loginValidator = null;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(Feature.AUTO_CLOSE_SOURCE, true);
try {
String res = result.getResponseAsString();//{"status":"true","msg":"success"}
loginValidator = objectMapper.readValue(result.getResponseAsString(), StatusResponses.class);
} catch (Exception e) {
e.printStackTrace();
}
StatusResponse class
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "status","msg" })
public class StatusResponses {
#JsonProperty("status")
public String getStatus() {
return status;
}
#JsonProperty("status")
public void setStatus(String status) {
this.status = status;
}
#JsonProperty("msg")
public String getMessage() {
return message;
}
#JsonProperty("msg")
public void setMessage(String message) {
this.message = message;
}
#JsonProperty("status")
private String status;
#JsonProperty("msg")
private String message;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}
#JsonSetter
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
}
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
StatusResponses loginValidator = null;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(Feature.AUTO_CLOSE_SOURCE, true);
try {
String res = result.getResponseAsString();//{"status":"true","msg":"success"}
loginValidator = objectMapper.readValue(res, StatusResponses.class);//replaced result.getResponseAsString() with res
} catch (Exception e) {
e.printStackTrace();
}
Don't know how it worked and why it worked? :( but it worked
In my case the problem was caused by my passing a null InputStream to the ObjectMapper.readValue call:
ObjectMapper objectMapper = ...
InputStream is = null; // The code here was returning null.
Foo foo = objectMapper.readValue(is, Foo.class)
I am guessing that this is the most common reason for this exception.
I could fix this error. In my case, the problem was at client side. By mistake I did not close the stream that I was writing to server. I closed stream and it worked fine. Even the error sounds like server was not able to identify the end-of-input.
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(jsonstring.getBytes());
out.close() ; //This is what I did
The problem for me was that I read the response twice as follows:
System.out.println(response.body().string());
getSucherResponse = objectMapper.readValue(response.body().string(), GetSucherResponse.class);
However, the response can only be read once as it is a stream.
I had a similar error today and the issue was the content-type header of the post request. Make sure the content type is what you expect. In my case a multipart/form-data content-type header was being sent to the API instead of application/json.
This error is sometimes (often?) hiding the real problem: a failure condition could be causing the content to be incorrect, which then fails to deserialize.
In my case, today, I was making HTTP calls and (foolishly) omitted to check the HTTP status code before trying to unmarshal the body of the response => my real problem was actualy that I had some authentication error, which caused a 401 Unauthorized to be sent back to me, with an empty body. Since I was unmarshalling that empty body directly without checking anything, I was getting this No content to map due to end-of-input, without getting any clue about the authentication issue.
I got this error when sending a GET request with postman.
The request required no parameters.
My mistake was I had a blank line in the request body.
In my case I was reading the stream in a jersey RequestEventListener I created on the server side to log the request body prior to the request being processed. I then realized that this probably resulted in the subsequent read to yield no string (which is what is passed over when the business logic is run). I verified that to be the case.
So if you are using streams to read the JSON string be careful of that.
A simple fix could be Content-Type: application/json
You are probably making a REST API call to get the response.
Mostly you are not setting Content-Type: application/json when you the request.
Content-Type: application/x-www-form-urlencoded will be chosen which might be causing this exception.
I know this is weird but when I changed GetMapping to PostMapping for both client and server side the error disappeared.
Both client and server are Spring boot projects.
Please Don't overthink this issue, simply handle the null pointer exception here as your response is received null and you will get your solution.
In my case I had race condition between 2 threads trying to write and read the file simultaneously. I added the "synchronized" keyword on my methods which writes and reads to the file and the error is gone.
For one, #JsonProperty("status") and #JsonProperty("msg") should only be there only when declaring the fields, not on the setters and geters.
In fact, the simplest way to parse this would be
#JsonAutoDetect //if you don't want to have getters and setters for each JsonProperty
public class StatusResponses {
#JsonProperty("status")
private String status;
#JsonProperty("msg")
private String message;
}
I have a method;
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(ObjectOne objectOne, ObjectTwo objectTwo)
Now I know I can post a single object in json format, just putting it into the body.
But is it possible to do multiple objects? If so, how?
You can not use your method like this as correctly stated by Tarlog.
However, you can do this:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects)
or this:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(BeanWithObjectOneAndObjectTwo containerObject)
Furthermore, you can always combine your method with GET parameters:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, #QueryParam("objectTwoId") long objectTwoId)
The answer is no.
The reason is simple: This about the parameters you can receive in a method. They must be related to the request. Right? So they must be either headers or cookies or query parameters or matrix parameters or path parameters or request body. (Just to tell the complete story there is additional types of parameters called context).
Now, when you receive JSON object in your request, you receive it in a request body. How many bodies the request may have? One and only one. So you can receive only one JSON object.
If we look at what the OP is trying to do, he/she is trying to post two (possibly unrelated) JSON objects. First any solution to try and send one part as the body, and one part as some other param, IMO, are horrible solutions. POST data should go in the body. It's not right to do something just because it works. Some work-arounds might be violating basic REST principles.
I see a few solutions
Use application/x-www-form-urlencoded
Use Multipart
Just wrap them in a single parent object
1. Use application/x-www-form-urlencoded
Another option is to just use application/x-www-form-urlencoded. We can actually have JSON values. For examle
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;
}
}
The one thing we need to get this to work is a ParamConverterProvider to get this to work. Below is one that has been implemented by Michal Gadjos of the Jersey Team (found here with explanation).
#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);
}
}
};
}
}
If you aren't scanning for resource and providers, just register this provider, and the above example should work.
2. Use Multipart
One solution that no one has mentioned, is to use multipart. This allows us to send arbitrary parts in a request. Since each request can only have one entity body, multipart is the work around, as it allows to have different parts (with their own content types) as part of the entity body.
Here is an example using Jersey (see official doc here)
Dependency
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Register the 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);
}
}
Resource class
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;
}
}
Now the tricky part with some clients is that there isn't a way to set the Content-Type of each body part, which is required for the above to work. The multipart provider will look up message body reader, based on the type of each part. If it's not set to application/json or a type, the Foo or Bar has a reader for, this will fail. We will use JSON here. There's no extra configuration but to have a reader available. I'll use Jackson. With the below dependency, no other configuration should be required, as the provider will be discovered through classpath scanning.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Now the test. I will be using cURL. You can see I explicitly set the Content-Type for each part with type. The -F signifies to different part. (See very bottom of the post for an idea of how the request body actually looks.)
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
Result: FooFoo; BarBar
The result is exactly as we expected. If you look at the resource method, all we do is return this string foo.foo + "; " + bar.bar, gathered from the two JSON objects.
You can see some examples using some different JAX-RS clients, in the links below. You will also see some server side example also from those different JAX-RS implementations. Each link should have somewhere in it a link to the official documentation for that implementation
Jersey example
Resteasy example
CXF example
There are other JAX-RS implementations out there, but you will need to find the documentation for it yourself. The above three are the only ones I have experience with.
As far as Javascript clients, most of the example I see (e.g. some of these involve setting the Content-Type to undefined/false (using FormData), letting the Browser handle the it. But this will not work for us, as the Browser will not set the Content-Type for each part. And the default type is text/plain.
I'm sure there are libraries out there that allow setting the type for each part, but just to show you how it can be done manually, I'll post an example (got a little help from here. I'll be using Angular, but the grunt work of building the entity body will be plain old 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>
The interesting part is the createRequest function. This is where we build the multipart, setting the Content-Type of each part to application/json, and concatenating the stringified foo and bar objects to each part. If you are unfamiliar multipart format see here for more info. The other interesting part is the header. We set it to multipart/form-data.
Below is the result. In Angular I just used the result to show in the HTML, with $scope.result = response.data. The button you see was just to make the request. You will also see the request data in firebug
3. Just wrap them in a single parent object
This option should be self explanatory, as others have already mentioned.
The next approach is usually applied in this kind of cases:
TransferObject {
ObjectOne objectOne;
ObjectTwo objectTwo;
//getters/setters
}
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
// object.getObejctOne()....
}
You can't put two separate objects in one single POST call as explained by Tarlog.
Anyway you could create a third container object that contains the first two objects and pass that one within the POS call.
I have also faced with these problem. Maybe this will help.
#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
It can be done by having the POST method declared to accept array of objects. Example like this
T[] create(#RequestBody T[] objects) {
for( T object : objects ) {
service.create(object);
}
}
Change #Consumes(MediaType.APPLICATION_JSON)
to #Consumes({MediaType.APPLICATION_FORM_URLENCODED})
Then you can putting multiple objects into the body
My solution is written for CXF, it appears to be quite simple.
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
#POST
#Path("paramTest")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public GenericResult paramTest(
#Multipart(value = "myData", type=MediaType.APPLICATION_JSON)
ObjectOne myData,
#Multipart(value = "infoList", type=MediaType.APPLICATION_JSON)
ObjectTwo[] infoList);
The test code for this with io.restassurred:
#Test
public void paramTest()
{
String payload1 = "" +
"{ \"name\": \"someName\", \"branch\": \"testBranch\" }";
String payload2 =
" [ { \"name\": \"cn\", \"status\": \"ts\" }," +
"{ \"name\": \"cn2\", \"status\": \"ts2\" } ] ]";
RestAssured.
given().
contentType("multipart/form-data").
multiPart("myData", payload1, "application/json").
multiPart("infoList", payload2, "application/json").
post(String.format("%s/paramTest", API_PATH)).
then().
statusCode(HttpStatus.SC_OK).
contentType(ContentType.JSON).
body("success", Matchers.equalTo(true));
}