Java REST service that accepts list of objects - java

I have to write a REST Service method that accepts a list of objects as parameter to calculate something and return a result.
I have so far:
#RequestMapping(value = "generateBill/{id}/{rates}")
public String generateBill(#PathVariable Long id, #PathVariable Rate rates[]) {
// do things
return "OK";
}
But I am sure that #PathVariable Rate rates[] is wrong.
I have to write the client part too and I also have no idea how to do it. It is the first time I have to write such a REST Service method.
Edit: Rate looks like this:
public class Rate {
Long version;
Double amount;
Date validFrom;
Date validUntil;
}

You should put your objects in the body of a POST request instead of using the URL :
#RequestMapping(value = "generateBill/{id}", method = RequestMethod.POST)
public String generateBill(#PathVariable Long id, #RequestBody BillingRequest billingRequest) {
// do things
}
Also, mapping directly a collection in the payload is not evolvable (you cannot add new "fields" outside the array), it's generally a good practice to wrap your array in a JSON object :
public class BillingRequest {
List<Rate> rates;
// Here you could add other fields in the future
}
Your HTTP request to call your service would look like this :
POST / HTTP/1.1
{
"rates" : [
{
"version" : 1,
"amount" : 33.3,
"validFrom" : "2016-01-01",
"validUntil" : "2017-01-01"
},
{
"version" : 2,
"amount" : 10.0,
"validFrom" : "2016-02-01",
"validUntil" : "2016-10-01"
}
]
}
One last piece of advice about your model :
Use java.time.LocalDate (or jodatime) instead of java.util.Date. If you need date+time, use java.time.ZonedDateTime (DateTime if you use jodatime)
Use java.math.BigDecimal to represent exact numbers. Floating point numbers like Double can lose precision
EDIT : I would suggest using Instant rather than ZonedDateTime, which is a timestamp with UTC timezone. Unless of course your domain requires different timezones.

First solution:
#RequestMapping(value = "generateBill/{id}/{rates}", method=RequestMethod.GET)
public String generateBill(#PathVariable Long id, #PathVariable Rate[] rates) {
// do things
return "OK";
}
Or second one (more Java style;) ):
#RequestMapping(value = "generateBill/{id}/{rates}", method=RequestMethod.GET)
public String generateBill(#PathVariable Long id, #PathVariable List<Rate> rates) {
// do things
return "OK";
}
This one you can call like this:
GET: http://localhost:8080/public/generateBill/1/1,2,3,4
Where 1.2,3,4 replace with your values, it depends od that what exactly is Rate ;)
EDIT
After your update, it looks like that you want to have POST method (you are posting list of rates) and then here is already answered question.
receiving json and deserializing as List of object at spring mvc controller

Other solution is to use JSON String format as a parameter and parse it afterwards. Something like
[
{
"rates":1,
"name":"rate1"
},
{
"rates":2,
"name":"rate2"
},
{
"rates":3,
"name":"rate3"
}
]
and after that parse the json to your object.

Related

When using Rest Assured, if I send a post request to submit data to a server, how can I intentionally leave out certain attributes in the json string?

Given this simple class representing a Person object:
#Builder
public class Person {
int age;
String name;
}
Using builder to create an instance of Person and posting it to a server using Rest Assured:
public class main {
public static void main(String [] args){
Person p1 = Person.builder().name("John").build();
Person p2 = Person.builder().age(29).build();
//CODE TO SEND POST REQUEST USING REST ASSURED
}
}
As I understand it, the corresponding json string being received by the server would look something like this:
p1: { "name": "John", "age": 0 }
p2: { "name": null, "age": 29 }
My question is: How can I intentionally leave out certain attributes of a class?
Lets say that the endpoint which I am posting this data to requires that the schema contains an age or name attribute, but for testing purposes, I want to leave out the age attribute so that what is received looks something like this:
p1: { "name": "John" }
p2: { "age": 29 }
I am thinking I could create a separate, similar class representing a person, but removing the attribute in question. This just seems wrong and inefficient. Is there a better way? Or is there some sort of stuff going on under the hood that by intentionally uninitializing an attribute, its left out in the post request? Thanks for your time.
It depends on how you are sending the Person class, but you could opt to filter out the null values by using #JsonInclude(Include.NON_NULL), or have a different mapper configured depending on your use case.
This answer describes a way how to do this with either Jackson or Gson.

How to retrieve a Long value from WebTau response body Map<String,?> representation?

I am retrieving the JSON request body from WebTau as a Map, e.g.:
Map<String,?> approvalMap = WebTauDsl.http.post(
callUrl, restCallHeader, restCallBody,
((header, body) ->
{
return body;
}
);
if( accessApprovalMap.get("id") instanceof Integer )
{
logger.info("id is Integer");
}
else if( accessApprovalMap.get("id") instanceof Long )
{
logger.info("id is Long");
}
From the logging code after the return ...
MonitorTest INFO : id is Integer
The question is what happens when the result is larger than MAX_INT is returned? BIGINT isn't very common at this point, but how would 'we' know? The string just looks like a number.
Is there a way to override the type of a JSON field?
related
How to retrieve JSON from request body using WebTau?
Behind the scenes WebTau uses com.fasterxml.jackson to parse JSON.
It automatically handles types like Long, Double, etc
Here is a WebTau test to show numbers conversion.
Given JSON response
{
"intValue": 30000,
"doubleValue": 100.43,
"longValue": 9223372036854775807
}
#Test
public void conversionOfNumbers() {
Map<String, ?> bodyAsMap = http.get("/large-numbers", (header, body) -> {
body.get("longValue").should(equal(9223372036854775807L));
body.get("doubleValue").should(equal(100.43));
body.get("intValue").should(equal(30000));
return body;
});
actual(bodyAsMap.get("longValue").getClass()).should(equal(Long.class));
actual(bodyAsMap.get("doubleValue").getClass()).should(equal(Double.class));
actual(bodyAsMap.get("intValue").getClass()).should(equal(Integer.class));
}
I am thinking that your response may not have a long enough number in your test.
If you provide more info on what you plan to do with the actual numbers, I may suggest an alternative way of achieving it.

Passing json object to an endpoint developed with spring

I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/

Java 8 - Hashmap cannot find and return a ZonedDateTime value

I'm working on a example project where we need to store some values in a Hashmap and fetch them via REST services.
POSTing the data as
{"timestamp":"2015-09-01T16:40:00.000Z", "temperature":"27.2"}
Save method:
#PostMapping
public ResponseEntity<?> createCalc(#Valid #RequestBody Calc calc) {
store.add(measurement);
}
...
Store class
private HashMap<ZonedDateTime, Calc> calcHashMap;
...
public void add(Calc calc) {
calcHashMap.put(calc.getTimestamp(), calc);
}
After saving we wanted to get data from Hashmap, none of the below can find it.
http://localhost:8000/measurements/2015-09-01T16:40:00.000Z
or
http://localhost:8000/measurements/2015-09-01T16:40Z
Method we are using is
#GetMapping("/{timestamp}")
public ResponseEntity<Calc> getCalc(#PathVariable ZonedDateTime timestamp) {
Calc calc = process.fetch(timestamp);
Process.java class
public Calc fetch(ZonedDateTime timestamp) {
return calcMap.get(timestamp);
}
Adding that to a Hashmap and Timestamp as the key. We are seeing these differences:
When printing the value in console using System.out.println 2015-09-01T16:40Z
Return from REST GET method in POSTMAN shows this 2015-09-01T16:40:00Z
While the actual value is 2015-09-01T16:40:00.000Z
I need to find the stores timestamp and return the object, but is not being found because of the differences above. How to solve this?
After some research i was able to solve this as below
for (ZonedDateTime zdt : measurementHashMap.keySet()) {
if (timestamp.toInstant().compareTo(measurementHashMap.get(zdt).getTimestamp().toInstant()) == 0) {
return measurementHashMap.get(zdt);
}
}
Option 2:
There is another option to convert ZonedDateTime to OffSetDate and then compare. Ref: https://rextester.com/SIIIW97667
In your Calc class, you can use #JsonFormat from Jackson library and give the required pattern you want. In this case, "yyyy-MM-dd'T'HH:mm:ss.SSSX". Also, I suggest you to use OffsetDateTime directly instead of conversion.

Iterate though array of object in java

I am trying to loop over the array of objects in java. I'm posting this value from client side to server side which is java.
"userList": [{
"id": "id1",
"name": "name1"
},
{
"id": "id2",
"name": "name2"
}]
Now I want to get the value of each id and name. I tried the code below:
for (Object temp : userList)
System.out.print(temp);
System.out.print(temp.getId());
}
But the output I get is:[object Object]
I'm sorry for this stupid question. But how will I get the value of id and name?
You're getting [object Object] because you didn't turn your JavaScript object into JSON on the client side before sending it to your server--you need to use something like JSON.stringify(object) in the browser.
Next, you will need to unpack your JSON into some sort of Java structure. The preferable way to do this is to let an existing tool such as Jackson or Gson map it onto a Java object that looks like:
class User {
String id;
String name;
}
How to do this will depend on your framework, but Spring MVC (for example) supports it mostly automatically.
Implement the toString method for your class according to how you want the printed output to look.
For example...
public class User {
private String id;
private String name;
// Constructors, field accessors/mutators, etc...
#Override
public String toString() {
return String.format("User {id: %s, name: %s}", this.id, this.name);
}
}
Your question does not have complete information. You certainly are skipping steps.
Before you start using the object in java you need to cast the object.
ArrayList<User> convertedUserList = (ArrayList<User>)userList;
for (User temp : convertedUserList)
System.out.print(temp);
System.out.print(temp.getId());
}

Categories