I'm developing a Web App using Spring 4 MVC. I want to know If I can validate JSON request objects with javax.validation API. For example I have this chunk of my entity code:
...
#JsonProperty("cheFecha")
#NotNull
#Column(name = "che_fecha")
#Temporal(TemporalType.DATE)
#DateTimeFormat(style = "M-")
private Date SsiCheque.cheFecha;
#JsonProperty("cheMonto")
#NotNull
#JsonSerialize(using = CurrencySerializer.class)
#Column(name = "che_monto", precision = 10, scale = 2)
private BigDecimal SsiCheque.cheMonto;
...
I have the controller code:
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
public #ResponseBody SsiCheque addChecks(#Valid SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("add" + result.getErrorCount());// Zero when there are errors
return ssiCheque;
}
And finally I have the jQuery code:
var formData = $("#formAddChecks :input").serializeArray();
$.ajax({
type: "POST",
url: "addCheck",
data: formData,
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
The JSON object is arriving correctly to the controller but I want to validate the JSON with the entity javax.annotations API. What I have seen is only using custom validators and "rewrite" the validation code.
Is this the only way to validate JSON?
Thanks in advance!
UPDATE 1
I followed the #James Massey suggestions and my code looks like this right now:
Controller
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
#ResponseBody
public SsiCheque addChecks(#Valid #RequestBody SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("agregar " + result.getErrorCount());
return ssiCheque;
}
Javascript file
var ssiCheque = {
cheNumero : $("#formAddChecks cheNumero").val(),
cheRecepto : $("#formAddChecks cheReceptor").val(),
cheMonto : $("#formAddChecks cheMonto").val(),
cheFecha : $("#formAddChecks cheFecha").val(),
cheConcepto : $("#formAddChecks cheConcepto").val()
};
$.ajax({
type: "POST",
contentType: "application/json",
url: "addCheck",
data: ssiCheque,
dataType: "json",
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' /*+ request.responseText*/ + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
But I'm getting an 400 Error (Incorrect request) when I submit the form and execute the Ajax function. I have faced this error before when the json object format and the controller specs were incompatible, but in this time I don't know why can be the error.
Thanks again!
I have solved my validations in another way. Suppose I have and Agent Object:
public class Agent {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
}
I would like to validate :
(1) userID>0
(2) name is mandatory
(3) isVoiceRecorded and isScreenRecorded can be true only if isOnCall is true.
In order to do so I need to add dependency :
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
Now look how Agents class looks like:
#NoArgsConstructor
#ToString
#EqualsAndHashCode(of = "userID")
#CheckBools
public class Agent {
#Min(0)
public int userID;
#NotNull(message = "Name cannot be null")
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public LocalDateTime startEventDateTime;
}
(1) #Min(0) - solves userID>0
(2) #NotNull(message = "Name cannot be null") - solves name is mandatory, and you have example how to specify error message
(3) #CheckBools annotation defined by me, at the class level which checks isVoiceRecorded and isScreenRecorded can be true only if isOnCall is true.
#Documented
#Constraint(validatedBy = MyConstraintValidator.class)
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface CheckBools {
String message() default "'isVoiceRecorded' or 'isScreenRecorded' can be true only if you are on call";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
In the following class you define the rule
public class MyConstraintValidator implements ConstraintValidator<CheckBools, Agent> {
#Override
public void initialize(CheckBools constraintAnnotation) {
}
#Override
public boolean isValid(Agent value, ConstraintValidatorContext context) {
if (!value.isOnCall && (value.isVoiceRecorded || value.isScreenRecorded))
return false;
else return true;
}
}
At the controller level :
#RestController
#RequestMapping("Myteamview")
public class MyteamviewController {
#Autowired
AgentInfo agentInfo;
#RequestMapping(path = "agents", method = RequestMethod.POST)
public ResponseEntity<Boolean> addOrUpdateAgent(#Valid #RequestBody Agent agent) {
ResponseEntity<Boolean> responseEntity = new ResponseEntity<>(agentInfo.addAgent(agent),HttpStatus.OK);
return responseEntity;
}
}
Note: The important is that you specify #Valid before #RequestBody Agent
There appear to be a few problems here:
Your object structure seems weird. Why are your fields referencing an object type? private Date SsiCheque.cheFecha seems to be a totally non-sensical field.
You generally design your UI to send through a JSON object that can be mapped directly into your Java object. So if your object looked like this:
public class Example {
#NotNull
#Digits(fraction = 2, integer = 10)
private Integer foo;
#NotEmpty
private String bar;
#NotEmpty
private String[] baz;
}
Then your JSON structure would be something like this:
{
"example": {
"foo": 1,
"bar": "Pineapple",
"baz": [
"This is a string",
"So is this"
]
}
}
Which can be used by Jackson to map straight into your object.
You would then write your controller method like this assuming that you had the Jackson JAR included in your project classpath:
#RequestMapping(value = "/example", method = RequestMethod.POST)
#ResponseBody
public Example(#Valid #RequestBody Example example, BindingResult result) {
if(result.hasErrors()){
//A validation has failed, return an error response to the UI
} else {
exampleService.createOrUpdate(example);
return example;
}
}
The important part is that your object is the request body and you use the #RequestBody annotation, as Jackson uses this as a signal to construct your object using the JSON present in your HTTP Request Body. The only downside to this method is that you may have to construct your request JSON programmatically. This is trivial to do with JavaScript however.
(I'm going to assume some sensible input id defaults here, and that you are familiar with the jQuery DOM manipulation/selection syntax)
var bazArray = [];
$.forEach($("#bazContainer"), function (baz, i){
bazArray.push(baz);
});
var example = {
foo: $("#fooInput").val(),
bar: $("#barInput").val(),
baz: bazArray
};
You pass in your example object to your request in the data field, and if you specify that it is of type application/json then jQuery will automatically call JSON.stringify on your example object.
Hopefully this all makes sense.
SOLUTION (Updated by questioner: Jessai)
I checked this question: Spring MVC 400 Bad Request Ajax.
In summary what I did:
Create an object to be parsed with JSON.stringify and send it to the controller.
In the controller I set the method with #ResponseBody and #RequestBody as #James Massey said.
In the entity I added #JSONProperty (I had these already) and #JSONIgnore (I added to cheId field) annotations to the fields.
Javascript:
var ssiCheque = {
cheNumero : $("#formAddChecks #cheNumero").val(),
cheRecepto : $("#formAddChecks #cheReceptor").val(),
cheMonto : $("#formAddChecks #cheMonto").val(),
cheFecha : $("#formAddChecks #cheFecha").val(),
cheConcepto : $("#formAddChecks #cheConcepto").val()
};
$.ajax({
type: "POST",
contentType: "application/json",
url: "addCheck",
data: JSON.stringify(ssiCheque),
dataType: "json",
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' /*+ request.responseText*/ + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
Controller
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
#ResponseBody
public SsiCheque addChecks(#Valid #RequestBody SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("agregar " + result.getErrorCount());
return ssiCheque;
}
Thanks!
Related
this may or may not be a somewhat long post, but I'm going to be pasting every single piece of information relating to this issue and the method I am testing, from Controller class with method to the a.jax snippet. I have asked about 4-6 developers and no one can find out the reason why its giving me a 415 error instead of a 200, because it just seems like I am doing everything correct. I just need some fresh eyes and new perspectives, hopefully someone could help me solve this. I will be pasting the classes and the relevant pieces now, and then a couple comments after the snippets.
Controller class
#Controller
#RequestMapping(value = "/user")
public class Controller
{
#Autowired
private Service service;
public Controller() {
}
#RequestMapping(value = "/landing/{age}/{name}/{title}/{family}/{gender}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public #ResponseBody String update(#RequestBody HouseModel model, #PathVariable int age, #PathVariable String name, #PathVariable String title, #PathVariable String family, #PathVariable String gender)
{
String result = service.update(model, age, name, title, family, gender);
// this will just return the string "Success" if update works or "Failed" if query is
wrong or not found
return result;
}
Service Class
#Service
public class Service
{
#Autowired
Dao dao;
public Service() {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
return dao.update(HouseModel model, int age, String name, String title, String family);
}
}
Dao class
#Repository
public class Dao
{
public Dao () {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
String result = "";
//some long query that updates the table and will populate result as "Success" or "Failed"
return result
}
}
Controller test class
#EnableWebMvc
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:applicationContext-testing.xml",
"classpath:applicationContext-EIPW.xml"})
public class ControllerTest {
#Autowired
private Controller controller;
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void test_update() throws Exception {
String URI = "/user/landing/22/Test/Mr/Yes/Male";
String content = "{\n \"HouseId\": 5,\n \"DateOfPurchase\": \"2019-01-01\",\n \"Price\": 100,\n \"Floors\": 5,\n \"Style\": \"Victorian\",\n}";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.put(URI).contentType(MediaType.APPLICATION_JSON).content(content).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andDo(MockMvcResultHandlers.print()).andReturn();
}
j.ajax
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "text"
printed error message
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /user/landing/22/Test/Mr/Yes/Male
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = controller.Controller
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 415
Error message = null
Headers = {Accept=[application/octet-stream, */*, text/plain;charset=ISO-8859-1, */*, application/xml, text/xml, application/*+xml, application/x-www-form-urlencoded, multipart/form-data]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Some Comments:
So I've had over 50 stack overflow tabs open relating to the same problem, and a lot of them had similar solutions that seemed so simple. Here are some, if not all of the things I did in attempts to try and solve this problem:
Switched around the content-type and accepts headers of requestBuilder to be MediaTypes of APPLICATION_JSON or APPLICATION_JSON_VALUE or ALL_VALUE, etc
Added produces or consumes = "application/json" or MediaType.APPLICATION_JSON/APPLICATION_JSON_VALUE/ALL_VALUE into the requestMapping().
Played around with a.jax to change content-type or accepts around
A couple of other things that I don't remember, but alas the 415 status is still here
I also do have setters and a default constructor in the HouseModel, and default constructors in every layer. I am 99.9% sure I have tried almost everything, if not everything, unless I am just missing something and am being stupid then yeah. I sent the request with the body as JSON raw an as:
{
"HouseId": 5,
"DateOfPurchase": "2019-01-01",
"Price": 100,
"Floors": 5,
"Style": "Victorian",
}
and it returned back success, I will attach its headers here:
[![Picture Link][1]][1]
[1]: https://i.stack.imgur.com/AqKnY.png
There is something interesting though, I did get one method to work but it required no arguments in its parameters, it was just a get method (dao calls database to store stuff in a list):
**Controller method**
#RequestMapping(value = "/levels", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody String getLevels() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// there is a long logic after this map to populate the map
Map<LevelObject, List<LevelObject>> map = new HashMap<LevelObject, List<LevelObject>>();
return mapper.writeValueAsString(map);
}
This is such a simple test and it worked perfectly fine, giving me a status 200 and my expected result.
**Test class method**
#Test
public void test_getLevels() throws Exception {
String URI = "/user/levels";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn();
}
**j.ajax for the getLevels method**
$j.ajax({
type: "POST",
url: "user/levels",
async: false,
dataType: "json",
Thank you so much!
Though I am not a front-end developer, I am sure that problem is in below line
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
)
Reason - I tested your code locally. Code works fine from postman, if I select request body as raw and type as JSON
But if select request body as raw and type as "TXT". I am getting same error as you.
[![enter image description here][2]][2]
So, I am sure that your request body is not being built as JSON. rather it is being sent as some other format. Hence, the error.
[2]: https://i.stack.imgur.com/cqSCC.png
Also, you can try to change dataType: "text" to dataType: "json"
Please try the below,
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data:
buildEdit(editRecords[i], ecRecord),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "json"
Remove converting the json to String
Change the dataType to json
I am passing JSON data from jQuery to my Java controller and I am using #RequestBody, but I am getting an exception saying:
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
The data which I am passing is:
myData = {
"source": "CSS",
"type": "CSS2",
"typeValue": "value",
"textarea_value": " desc"
}:
The AJAX call I am using to pass this data is:
$.ajax({
url: './common/deleteData',
type: 'POST',
data: myData,
success: function(data) {
alert("Successfully Deleted Source..");
},
error: function(data) {}
});
My Java Controller is as below
#RequestMapping(value = "/common/deleteData", method = RequestMethod.POST, consumes = {"application/x-www-form-urlencoded; charset=UTF-8"})
public String deleteData(#RequestBody SourceDelete sourcedelete, final HttpServletRequest request, final RedirectAttributes rdtAttribs) throws ApplicationException
{
LOGGER.entry("Deleting the Merge Preference Details");
System.out.println(sourcedelete.getSource());
return null;
}
My POJO object is as below:
public class SourceDelete {
private String source;
private String type;
private String typeValue;
private String textarea_value;
//Setters and Getters
}
Can someone please help me figure out why I am getting this error and how I should fix it.
remove the #RequestBody annotation,
#RequestMapping(value = "/common/deleteData", method = RequestMethod.POST, consumes = {"application/x-www-form-urlencoded; charset=UTF-8"})
public String deleteData(SourceDelete sourcedelete, final HttpServletRequest request, final RedirectAttributes rdtAttribs) throws ApplicationException
{
LOGGER.entry("Deleting the Merge Preference Details");
System.out.println(sourcedelete.getSource());
return null;
}
I've read 3/4 posts on Stack plus many other examples to try figure this out but I've no clue ! Need some pointers please !!
Creating my first Ajax update through Spring-MVC and I keep getting a Status 415 being returned by my submission with The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request accept
JQuery... Version 3.1.1
function updateScore () {
$("div#results").append("<p>Posting User/Game ID " + this.id + " Value " + this.value + "</p>");
var prediction = {}
prediction["id"] = this.id;
prediction["value"] = this.value;
$.ajax({
type : "POST",
contentType : "application/json",
url : "/tournament/setPrediction.html",
data : JSON.stringify(prediction),
dataType : 'json',
timeout : 100000,
success : function(data) {
console.log("SUCCESS: ", data);
displayResult(data, "success");
},
error : function(e) {
console.log("ERROR: ", e);
displayResult(e, "error");
},
done : function(e) {
console.log("DONE");
displayResult(true, "done");
}
});
}
Controller... Spring version 4.3.5
#RestController
public class PredictionAjaxController {
#ResponseBody
#RequestMapping(value = "/setPrediction.html", consumes = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.POST, headers="Accept=application/json")
public Prediction setUserPrediction(#RequestBody PredictionPojo prediction) {
Prediction result = new Prediction();
System.out.println("AJAX call made in controller");
return result;
}
}
Finally a very simple POJO for the JSon to map to
public class PredictionPojo {
private String id;
private String value;
Getters & Setters... ()
}
I've added different things onto the controller now to try and resolve, didn't start with it all ! I'm completely confuddled !
Should be so simple...
DH
You have an error in your ajax call, you are sending a string instead of a JSON object. Also I don't think is necessary to specify the consumes and headers attributes in you #RequestMapping annotation in your setUserPrediction method, The PredictionAjaxController is already defined as a RestController. Your ajax should be:
$.ajax({
// .......
data : prediction,
// .......
});
I am capturing a hierarchical data using Jquery based Jstree library.Data is in a JSON format, I want to capture and bind this data to my bean class(JstreeJson.java).Here is what I have tried so far..
Ajax call :
function getJSON() {
var jstree = $('#jstree1').jstree(true).get_json('#', {flat:true});
console.log(JSON.stringify(jstree));
$.ajax({
type:"POST",
url:"createObjective",
data : { jstree: jstree },
dataType :"json",
success : function(result) {
console.log(jstree);
console.log(result);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
console.log(textStatus, errorThrown);
}
});
}
console.log output:
[{"id":"j1_1","text":"Simple root node","icon":true,"li_attr":{"id":"j1_1"},"a_attr":{"href":"#","id":"j1_1_anchor"},"state":{"loaded":true,"opened":false,"selected":false,"disabled":false},"data":{},"parent":"#"}]
controller
#RequestMapping(value="/createObjective",method=RequestMethod.POST)
public #ResponseBody String createObjective(#RequestBody JstreeJson jstree)
{
System.out.println(jstree);
return "done";
}
Bean class
public class JstreeJson
{
private String id;
private String text;
private String parent;
// getters and setter
}
I have tried adding consumes and Headers but it didnt have any effect on my output
#RequestMapping(value="/createObjective",method=RequestMethod.POST,consumes="application/json",headers = "content-type=application/x-www-form-urlencoded")
Try this #RequestMapping:-
#RequestMapping(value="/createObjective" method = RequestMethod.POST,consumes= {"application/json;charset=UTF-8"}, produces={"application/json;charset=UTF-8"})
public #ResponseBody String createObjective(#RequestBody JstreeJson jstree)
{
System.out.println(jstree);
return "done";
}
Or you can keep #RequestMapping simple as bellow:
#RequestMapping(value="/createObjective")
Spring will take care rest of the attributes of #RequestMapping depending on request.
I ma using Spring MVC and trying to use jQuery. I have this on my web page:
$(document).ready(function () {
var entity = {mag: "status_key", paper: "View10"};
$("#btn").click(function () {
$.ajax({
url: "ajaxJsonPost",
type: 'post',
dataType: 'json',
data: JSON.stringify(entity),
contentType: 'application/json',
});
});
});
Spring server has this:
#RequestMapping(value = "ajaxJsonPost", method = RequestMethod.POST)
public void postJson(#RequestBody Entity en) throws IOException {
System.out.println("writing entity: " + en.toString());
}
OK, Entity cames to server. BUT browser console prints 404 not found. I know that my POST request needs any response. In the Internet I've found solution which recommends me to return ResponseEntity object, OR use annotation #ResponseStatus. They both return HttpStatus well, but I don't know in which cases I should use them. What is the best way?
#Controller
#RequestMapping("/apipath")
public class SomeController {
#RequestMapping(value = "/ajaxJsonPost", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String postJson(#RequestBody final Entity en) {
System.out.println(en.toString());
//assuming you have a class "EntityService" and
//it has a method postData
//which takes Entity object as parameter and pushes into database.
EntityService.postData(en);
System.out.println("added");
return "success";
}
}
Entity object on the Server side
#JsonAutoDetect
public class Entity {
private String mag;
private String paper;
public String getMag() {
return mag;
}
public void setMag(final String mag) {
this.mag = mag;
}
public String getPaper() {
return paper;
}
public void setPaper(final String paper)
this.paper = paper;
}
}
ajax
$(document).ready(function () {
var entity = {mag: "status_key", paper: "View10"};
$("#btn").click(function () {
$.ajax({
url: "/apipath/ajaxJsonPost",
type: 'post',
dataType: 'json',
data: JSON.stringify(entity),
contentType: 'application/json',
success : function(response) {
alert(response);
},
error : function() {
alert('error');
}
});
});
});
And as far as why and when to use #ResponseStatus and #ResponseEntity, there is already a short and simple answer here by #Sotirios Delimanolis. When use #ResponseEntity .
It says :
ResponseEntity is meant to represent the entire HTTP response. You can
control anything that goes into it: status code, headers, and body.
#ResponseBody is a marker for the HTTP response body and
#ResponseStatus declares the status code of the HTTP response.
#ResponseStatus isn't very flexible. It marks the entire method so you
have to be sure that your handler method will always behave the same
way. And you still can't set the headers. You'd need the
HttpServletResponse or a HttpHeaders parameter.
Basically, ResponseEntity lets you do more.