I am currently working on implementing REST webservices into existing system. This system is using Spring in version 2 (particularly 2.5.6.SEC02). I can't upgrade it to version 3 as it could break existing system components. We didn't make the rest of this system, don't have source codes and don't want to lose warranty, so Spring version should basically stay as it is :)
The question is, how can I implement Rest WS with automatic DTO serialization from/to JSON? I have appropriate Jackson libraries on the classpath. Spring 2 doesn't seem to know about #RequestBody and #ResponseBody yet. Are there any other annotations that can be used, or some alternative way?
You may need to manually parse the JSON String and write it to the response for this to work for you. I suggest using the jackson2 API.
https://github.com/FasterXML/jackson
First, accept a json String as the argument from the request, and then manually parse the String to and from a POJO using the jackson ObjectMapper.
Here's the jQuery/JavaScript:
function incrementAge(){
var person = {name:"Hubert",age:32};
$.ajax({
dataType: "json",
url: "/myapp/MyAction",
type: "POST",
data: {
person: JSON.stringify(person)
}
})
.done(function (response, textStatus, jqXHR) {
alert(response.name);//Hubert
alert(response.age);//33
//Do stuff here
});
}
The person pojo:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
//Allows name or age to be null or empty, which I like to do to make things easier on the JavaScript side
#JsonIgnoreProperties(ignoreUnknown = true)
public class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
And here's the the controller:
import com.fasterxml.jackson.databind.ObjectMapper;
/*snip*/
#Controller
public class MyController{
//I prefer to have a single instance of the mapper and then inject it using Spring autowiring
private ObjectMapper mapper;
#Autowired
public MyController(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
}
#RequestMapping(value="/myapp/MyAction", method= {RequestMethod.POST})
public void myAction(#RequestParam(value = "person") String json,
HttpServletResponse response) throws IOException {
Person pojo = objectMapper.readValue(new StringReader(json), Person.class);
int age = pojo.getAge();
age++;
pojo.setAge(age);
objectMapper.writeValue(response.getOutputStream(),pojo);
}
}
You can try a DIY approach with Spring MVC controllers and JSONObject from json.org. Just use it to json-serialize your returned objects and flush it down the response, with the appropiate headers.
It has its pitfalls (I would recommend you use a simple wrapper class with a getter when you try to send a collection), but I have been happy with it for some years.
Related
I have a POST endpoint which accepts a JSON as request body.
#Path("/drink")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class DrinkResource {
#POST
public Drink getDrink(Fruit fruit) {
return new Drink(fruit.getName());
}
}
The request body is supposed to be deserialized into this POJO :
public class Fruit {
private final String name;
#JsonCreator
public Fruit(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I'm using Jackson for the deserialization.
Is it possible to make the deserialization fail when the JSON in the request body has duplicate keys ?
For example, if the request body looks like this : {"name" : "banana", "name" : "orange"}, I would like to get a 500 status code or another kind of error instead of having the json deserialized with the last property.
Basically, I'm looking for a solution with the same logic as the JsonParser.Feature.STRICT_DUPLICATE_DETECTION with the ObjectMapper but for a POST endpoint.
I'm also using quarkus so I don't know if there is a property for this. Something similar to quarkus.jackson.fail-on-unknown-properties=true but for the duplicate properties.
Add the following:
#Singleton
public class MyCustomizer implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
}
}
If you do, the ObjectMapper that Quarkus uses will throw a JsonParseException thus leading to a HTTP 400 response.
For some reason java can't map DTO with requestBody and all values are default ones, as for request it works, with payload for ex. "{"productId":1,"commitment":6,"returnMonths":"2"}"
DTO
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
// contructers
}
Controller :
#PostMapping(value = "/calculate", consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String calculatePrice(#RequestBody Request request) {
productService.calculatePrice(request);
return "Success";
}
front request:
submit: async function() {
let request = {
productId: this.productSelected,
commitment: this.optionSelected,
returnMonths: this.input
};
let data = await getCalculation(request);
console.log(data);
}
DTO maps as:
productId : 0
commitment : 0
returnMonths : null
Tried an exact copy of your code and it worked when tested with Postman. This makes me think it's either something to do with the FE or maybe some issue in the service. I'd check if the Frontend really sends the data.
Try to annotation Request class with #AllArgsConstructor like:
#AllArgsConstructor
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
}
If your request body contains properties that is date such as LocalDateTime, make sure to format it in your DTO using #JsonFormat(pattern="") respecting the input value.
Can someone please help me how to get a JSON String in a Webservice. I's sending JSON to my /api/register that looks like:
{"name":"MyName","surname":"MySurename","email":"mail#asd.de","street":"MyStreet","number":"3","zip":"12345","city":"myCity","pass":"myPassword"}
Here is my register.java file:
#Path("/register")
#Stateless
public class RegisterWS {
#EJB
UserBS userBS;
#POST
#Consumes(MediaType.APPLICATION_JSON)
public void createUser(){
// code to get data from json
userBS.createUser(name, surename, email, adress, number, zip, city, password);
}
}
My AngularJS Controller and Service. The Data comes from a form, that is parsed to a JSON object.
app.service('RegisterService', function ($http) {
return {
registerUser : function(user) {
$http.post('http://localhost:8080/myApp/api/register')
.success(function (user) {
return user;
})
.error(function (data) {
// failed
});
}
}
});
app.controller('RegisterCtrl', function($scope, RegisterService) {
$scope.register = function(){
RegisterService.registerUser(angular.toJson($scope.user));
}
});
You should have a POJO, which maps to the received JSON object, for example a User class. In this case this would be a very simple Java Bean, with mostly String properties for each field in the JSON.
#XmlRootElement
public class User {
String name;
String surname;
String email;
String street;
Integer number;
String zip;
String city;
String pass;
}
Of course you would use private fields, with getters and setters, but I did not want to add clutter. By the way the #XmlRootElement is a JAXB annotation, and JAX-RS uses JAXB internally.
After you have this, you just need to change your method like this
#POST
#Consumes(MediaType.APPLICATION_JSON)
public void createUser(User user) {
...
}
You should not need to change anything on the AngularJS side, as the default for the $http.post method is JSON communication.
For your Java code, you have to add a User POJO, I dont know if you will use some persistence API or not, so the user POJO must implement serializable to output user object as JSON.
Here's a an example of REST app with EJB ... : http://tomee.apache.org/examples-trunk/rest-on-ejb/README.html
For your client app, you need to specify the content type : "Content-Type" = "application/json"
See this questions: change Content-type to "application/json" POST method, RESTful API
I'm working on adapting angular-ui-tree to persist the tree structure in a one-to-many entity model. The piece I am missing is how to have Spring/Jackson interpret the following POST body
data: "{
"id":1.7976931348623157e+308,
"name":"New Category -- Need Name",
"parent":6,
"type":"create"
}"
that is serialized and delivered using a $http service method in Angular:
$http.post("/rest/category",
{data: angular.toJson(updateData)}
)
.success(function(data, status, headers, config){
return [data, status];
})
.error(function(data, status, headers, config){
return [data, status];
})
Right now, I only get error 405s with the address, with the return value stating that
Request method 'POST' not supported
So, I modified the controller below to take in a DummyCategory object from the RequestBody:
#RequestMapping(value = "/rest/category", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public List<Category> createCategory(#RequestBody DummyCategory data ){
log.info("Start creating category " + data);
}
which is this entity:
#JsonDeserialize(using = DummyCategoryDeserializer.class)
public class DummyCategory {
private String type;
private String name;
private Long id;
private int parent;
//getters and setters
}
and corresponding DummyCategoryDeserializer class:
public class DummyCategoryDeserializer extends JsonDeserializer<DummyCategory> {
#Override
public DummyCategory deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
String tempData = jp.getText();
DummyCategory dummyCategory = new DummyCategory();
return dummyCategory;
}
}
Since I am very new at this, my goal is to have a debugger breakpoint set at the deserializer method and work my way through the properties. However, after making these custom modifications, the $http service is still returning 405s on execution, and the breakpoints are not being hit.
I know that the server is functional, because all GET requests are returning expected values.
If you want to see the actual source code for this question, it is available on the category-taxonomy branch. The new deserializer classes are within /models and /utils
I just took the tutorial over at Spring.io http://spring.io/guides/gs/rest-service/ and created a simple rest service. But, does anybody know how I can return multiple objects in JSON format? If I for instance have a person class with a name and an id, how can I add three persons to /persons?
You can use the #ResponseBody annotation and just return whatever you want, providing that those objects can be jsonized.
For example, you can have a bean like this:
#Data
public class SomePojo {
private String someProp;
private List<String> someListOfProps;
}
and then in your controller you can have:
#ResponseBody
#RequestMapping("/someRequestMapping")
public List<SomePojo> getSomePojos(){
return Arrays.<String>asList(new SomePojo("someProp", Arrays.<String>asList("prop1", "prop2"));
}
and Spring by default would use its Jackson mapper to do it, so you'd get a response like:
[{"someProp":"someProp", "someListOfProps": ["prop1", "prop2"]}]
The same way, you can bind to some objects, but this time, using the #RequestBody annotation, where jackson would be used this time to pre-convert the json for you.
what you can do is
#RequestMapping("/someOtherRequestMapping")
public void doStuff(#RequestBody List<SomePojo> somePojos) {
//do stuff with the pojos
}
Try returning a list from the method:
#RequestMapping("/greetings")
public #ResponseBody List<Greeting> greetings(
#RequestParam(value="name", required=false, defaultValue="World") String name) {
return Arrays.asList(new Greeting(counter.incrementAndGet(),String.format(template, name)));
}