I am want to validate a JSON object, for length of an attribute. I am using #Size annotation to specify maximum length as shown below.
#JsonRootName("question")
public class QuestionJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "text", required = true)
private String label;
#JsonProperty(value = "answers", required = true)
private List<AnswerJson> answers;
}
#JsonRootName("answer")
public class AnswerJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "type", required = true)
private String type;
#JsonProperty(value = "label", required = true)
#Size(message = "size should not be long", max = 10)
private String label;
}
My request mapping in controller looks like:
#RequestMapping(value = "/api/answer", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public AnswerJson createQuestion(#RequestHeader(HttpHeaders.AUTHORIZATION) final String authorizationHeader, #Valid #RequestBody final QuestionJson questionJson) {
// some code here....
return answer;
}
UPDATE: Validation works on the outer elements eg. text in my case but fails on the nested list.
Every time we use #Valid annotation we also include a BindingResult instance as a method parameter, it contains the #Valid marked parameter errors if any:
public final #ResponseBody String theMethod(
final #Valid ValidableObjectImpl validableObject,
BindingResult result) {
try {
if (result.hasErrors()) {
for (FieldError error : result.getFieldErrors()){
// do something
}
// return error
}
} catch (Exception e) {
// ...
}
}
Found the solution. We need add #Valid annotation to the before the declaration of the nested object. eg
#JsonRootName("question")
public class QuestionJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "text", required = true)
private String label;
#JsonProperty(value = "answers", required = true)
#Valid
private List<AnswerJson> answers;
}
Related
I'm new to JPA.
I have a class like this
#Table(name="student")
#Entity
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int studentId;
#Column
#Size(max = 20)
private String name;
#Column
#Min(value = 2014)
#Max(value = 2020)
private int yearOfBirth;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "classroomId")
Classroom classroom;
//Getters and setters
}
My repository:
#Repository
public interface HocSinhRepository extends JpaRepository<Student, Integer> {}
My controller:
public class StudentController {
#Autowired
StudentRepository studentRepository;
#GetMapping(value = "/get")
public Page<Student> get(#RequestParam Optional<Integer> page, #RequestParam Optional<String> sortBy) {
return studentRepository.findAll(
PageRequest.of(page.orElse(0), 3, Sort.Direction.ASC, sortBy.orElse("name"))
);
}
}
By using Optional.orElse, I can assign a default value to the sortBy parameter if it's null. How can I do the same thing if the parameter is not null, but just a non-sensible string (like "asdasd")?
You can use the map operator of optionals.
So it would be
sortBy.map(o -> {
if (o.equalsIgnoreCase("asdasd")){
return "name";
} else {
return o;
}
}).orElse("name"));
This will make the optional return the value "name" when it is empty or when the containing value exists and is with ignore case "asdasd". In every other case it will return the original value it contained.
So your code will be adapted to be
return studentRepository.findAll(
PageRequest.of(page.orElse(0), 3, Sort.Direction.ASC,
sortBy.map(o -> {
if (o.equalsIgnoreCase("asdasd")){
return "name";
} else {
return o;
}}).orElse("name"));
));
variation on #xerx593's VALID_SORT_FIELDS and making spring's #RequestParam optional
public static final Set<String> VALID_SORT_FIELDS = Set.of("name", "studentId");
#GetMapping(value = "/get")
public Page<Student> get(
#RequestParam(required = false, defaultValue = "0") Integer page,
#RequestParam(required = false, defaultValue = "name") String sortBy) {
String sortByOrDefault = VALID_SORT_FIELDS.contains(sortBy) ? sortBy : "name";
return studentRepository.findAll(PageRequest.of(page, 3, Sort.Direction.ASC, sortByOrDefault));
}
another variation:
#GetMapping(value = "/get")
public Page<Student> get(
#RequestParam(required = false, defaultValue = "0") Integer page,
#RequestParam(required = false, defaultValue = "name") String sortBy) {
String sortByOrDefault = SortConfig.STUDENTS.getSortableFields().contains(sortBy)
? sortBy
: SortConfig.STUDENTS.getDefaultSortField();
return studentRepository.findAll(PageRequest.of(page, 3, Sort.Direction.ASC, sortByOrDefault));
}
public enum SortConfig {
STUDENTS(List.of("name", "studentId")),
ANOTHER_ENTITY(List.of("field1", "field2"));
private String defaultSortField;
private List<String> sortableFields;
private SortConfig(List<String> sortableFields) {
this.sortableFields = sortableFields;
}
public String getDefaultSortField() {
return defaultSortField;
}
public List<String> getSortableFields() {
return sortableFields;
}
}
A common scenario would be:
To have the "valid field names" stored in a "data structure", e.g., like:
public static final Set<String> VALID_SORT_FIELDS = Set.of("name", "studentId"/*, all we "consider valid"...*/);
Then we can use it in our controller like:
#GetMapping(value = "/get")
public Page<Student> get(#RequestParam Optional<Integer> page, #RequestParam Optional<String> sortBy) {
final String sort = sortBy
.map( s ->
s != null && VALID_SORT_FIELDS.contains(s)?
s : "name" // or a consatant com.domain.my...DEFAULT_SORT
)
.get();
return studentRepository.findAll(
PageRequest.of(page.orElse(0), 3, Sort.Direction.ASC, sort)
);
}
When we want to ignore the case, we would adjust to:
...&& VALID_SORT_FIELDS.contains(s.toLowerCase(/*some (default) Locale*/))
Another/additional possible approach: validaion-api... but the wrapping Optional is a "challange"(??)...
Have the same DTO object for POST and PUT methods:
class AcmeRequest {
private String id;
#NotEmpty
private String productCode;
private String description;
}
For POST request I always expect to see productCode field, that's why I specified #NotEmpty annotation but when PUT request received productCode should be optional.
Is it possible some how just to skip #NotEmpty when request is PUT?
Every Hibernate Validator annotation has a groups parameter. Through interfaces, you can control which validations are activated. See more at docs.
In controller level, specify which groups must be activated with the #Validated annotation.
Below, there is a small example from one of my demo projects. I once had the same question as you.
Entity:
#Entity
#Table(name = "tasks")
#Getter #Setter
public class Task
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(message = "You can't provide a task ID manually. ID's are automatically assigned by our internal systems.", groups = {TaskInsertValidatorGroup.class})
#NotNull(message = "You must provide an id" , groups = TaskUpdateValidatorGroup.class)
private Integer id;
#NotBlank(message = "Task description cannot be empty")
#Length(max = 255 , message = "Task description length must not exceed 255 characters")
private String description;
#JsonProperty("is_completed")
#Column(name = "is_completed")
private Boolean isCompleted = false;
#CreationTimestamp
#JsonProperty("created_on")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "created_on", updatable = false)
private Timestamp creationDate;
#UpdateTimestamp
#JsonProperty("last_modified")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "last_modidied")
private Timestamp lastModificationDate;
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task = (Task) o;
return id.equals(task.id);
}
#Override
public int hashCode()
{
return Objects.hash(id);
}
}
Interfaces:
public interface TaskInsertValidatorGroup {}
public interface TaskUpdateValidatorGroup{}
Controller:
RestController
#RequestMapping("/api")
public class TaskRestController
{
#Autowired
private TaskService taskService;
#GetMapping("/tasks/{id}")
public ResponseEntity<?> getTask(#PathVariable Integer id)
{
return new ResponseEntity<>(taskService.findTask(id),HttpStatus.OK);
}
#GetMapping("/tasks")
public ResponseEntity<?> getTasks()
{
return new ResponseEntity<>(taskService.findAllTasks(),HttpStatus.OK);
}
#PostMapping("/tasks")
public ResponseEntity<?> addTask(#Validated(TaskInsertValidatorGroup.class) #RequestBody Task task)
{
taskService.saveTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task added")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks" , method = RequestMethod.PATCH)
public ResponseEntity<?> updateTask(#Validated(TaskUpdateValidatorGroup.class) #RequestBody Task task)
{
taskService.updateTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Updated")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> removeTask(#PathVariable Integer id)
{
taskService.removeTask(id);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Deleted")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
}
I want to MAP my HTTP request parameter value directly to my DTO USING #JsonProperty on the basis of the variable name not by #JsonProperty value. I am not able to map the value to DTO because it's expecting request value according to the JsonProperty name. Is there anyway to disable #JsonProperty value while using the #RequestBody ?
JSON send by frontend:
{
"userId":"1",
"payMethod":"payMethod"
}
MyDto.class
public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
//getter setter
}
MyController.class
public class MyController{
#RequestMapping(value = "payment", method = RequestMethod.PUT)
public Integer PaymentUpdate(#RequestBody final MyDto myDto) throws JsonProcessingException {
}
you can do this by using multiple setter method for that DTO method. For example
Payload:
{
"userId":"1",
"payMethod":"payMethod"
}
then
MyDto.class public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
add one more setter relevant to the required variable name in the DTO class.
#JsonSetter("specifiedName")
void setUserId(String userId){
this.userId=userId
}
void setPayMethod(String payMethod){ // Will work for "BETAALMETHODE" variable name
this.payMethod=payMethod
}
#JsonSetter("payMethod")
void setPayMethod(String payMethod){
this.payMethod=payMethod
}
This will solve your problems and variable payMethod will assign in both the cases.
You can use JacksonMixin during csv parsing:
public abstract class MyDtoMixin {
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
}
ObjectMapper mapper = new ObjectMapper(); // or CsvMapper mapper = new CsvMapper();
mapper.addMixInAnnotations(MyDto.class, MyDtoMixin.class);
I'm working on a Spring Boot + Maven + Restful + Hibernate project! After creating the RestController for adding new Devices in database i'm getting this error:
2018-03-28 10:15:18.786 WARN 9286 --- [nio-9090-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.hhm.hsy.hibernate.models.Protocol` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"id":5,"protocolName":"ProtocolForTesting","port":5202}'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.hhm.hsy.hibernate.models.Protocol` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"id":5,"protocolName":"ProtocolForTesting","port":5202}')
at [Source: (PushbackInputStream); line: 1, column: 52] (through reference chain: com.hhm.hsy.hibernate.models.Device["protocol"])
Here is my first entity:
#Entity
#Table(name = "devices", catalog = "hqm")
public class Device implements Serializable {
private static final long serialVersionUID = -8311225474375837513L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "device_id", unique = true, nullable = false)
private Integer id;
#Column(name = "device_name", unique = true, nullable = false)
private String deviceName;
#ManyToOne
#JoinColumn(name = "protocol_id")
private Protocol protocol;
public Device() {
}
public Device(Integer id, String deviceName, Protocol protocol) {
this.id = id;
this.deviceName = deviceName;
this.protocol = protocol;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public Protocol getProtocol() {
return protocol;
}
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
And the second entity:
#Entity
#Table(name = "protocols", catalog = "hqm")
public class Protocol implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "protocol_id", unique = true, nullable = false)
private Integer id;
#Column(name = "protocol_name", unique = true, nullable = false, length = 45)
private String protocolName;
#Column(name = "port", nullable = false)
private Integer port;
#OneToMany(mappedBy = "protocol", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Device> devices = new HashSet<>();
public Protocol() {
}
public Protocol(Integer id, String protocolName, Integer port) {
this.id = id;
this.protocolName = protocolName;
this.port = port;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProtocolName() {
return protocolName;
}
public void setProtocolName(String protocolName) {
this.protocolName = protocolName;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
#JsonIgnore
public Set<Device> getDevices() {
return devices;
}
#JsonIgnore
public void setDevices(Set<Device> devices) {
this.devices = devices;
}
}
Controller:
#RestController
#RequestMapping(value = "/api/devices")
#ComponentScan({"com.hhm.hsy.pmcs.*"})
public class DevicesController {
#Autowired
#Qualifier(value = "deviceService")
GenericServiceIntf deviceService;
// get ALL DEVICE
#RequestMapping(value = "", method = RequestMethod.GET)
public Map<String, Object> getDevices() {
Map<String, Object> devicesMap = new HashMap<>();
devicesMap.put("devices", deviceService.getAll());
return devicesMap;
}
//save a new DEVICE
#RequestMapping(value = "", method = RequestMethod.POST, consumes = {"application/json"}, produces = {"application/json"})
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Device> addDevice(#RequestBody Device device) {
deviceService.save(device);
return ResponseEntity.accepted().body(device);
}
}
Service:
#Service("deviceService")
public class DeviceServiceImpl extends GenericServiceAbstractImpl<Device, Integer> implements Serializable{
private static final long serialVersionUID = 697655212967127150L;
#Autowired
public DeviceServiceImpl(#Qualifier("deviceDao") GenericDaoIntf genericDao) {
super(genericDao);
}
}
So when i'm trying to add a new device, i get the error i mentioned upper.I don't know what is causing this exception. When I try to add with post a new Protocol it's working, table is being created in the database correctly and I am getting the data correctly in GET request as well..Please help me, I'm new to springboot and restful... if some more information is required, please just inform me and i will post it! Thank you!
I tried to reproduce your problem: here, but everything works as expected.
I think it can be related with this bug.
You should try to reproduce bug with different jackson version.
EDIT:
One more thing: It looks like you try to construct Protocol instead of Device. Show us your deviceService, if you can.
Failed to read HTTP message:
org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Cannot construct instance of
`com.hhm.hsy.hibernate.models.Protocol
I'm attempting to use this dropwizard example and build off of it. I tried to add a column userName to the people table in Person.java like below
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "fullName", nullable = false)
private String fullName;
#Column(name = "jobTitle", nullable = false)
private String jobTitle;
#Column(name = "userName", nullable = false)
private String userName;
public Person() {
}
public Person(String fullName, String jobTitle, String userName) {
this.fullName = fullName;
this.jobTitle = jobTitle;
this.userName = userName;
}
I added the appropriate getters and setters, and equals method.
However I'm getting an error reading entity from input stream in this block.
#Test
public void testPostPerson() throws Exception {
final Person person = new Person("Dr. IntegrationTest", "Chief Wizard", "Dr. Wizard");
final Person newPerson = RULE.client().target("http://localhost:" + RULE.getLocalPort() + "/people")
.request()
.post(Entity.entity(person, MediaType.APPLICATION_JSON_TYPE))
--> .readEntity(Person.class);
assertThat(newPerson.getId()).isNotNull();
assertThat(newPerson.getFullName()).isEqualTo(person.getFullName());
assertThat(newPerson.getJobTitle()).isEqualTo(person.getJobTitle());
assertThat(newPerson.getUserName()).isEqualTo(person.getUserName());
}
the input stream error is caused by the following
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "code" (class com.example.helloworld.core.Person), not marked as ignorable (4 known properties: "fullName", "id", "userName", "jobTitle"])
will #JsonIgnoreProperties annotation at the class level solve this problem? Is this safe practice?
EDIT: PersonResource.java
#Path("/people/{personId}")
#Produces(MediaType.APPLICATION_JSON)
public class PersonResource {
private final PersonDAO peopleDAO;
public PersonResource(PersonDAO peopleDAO) {
this.peopleDAO = peopleDAO;
}
#GET
#UnitOfWork
public Person getPerson(#PathParam("personId") LongParam personId) {
return findSafely(personId.get());
}
#GET
#Path("/view_freemarker")
#UnitOfWork
#Produces(MediaType.TEXT_HTML)
public PersonView getPersonViewFreemarker(#PathParam("personId") LongParam personId) {
return new PersonView(PersonView.Template.FREEMARKER, findSafely(personId.get()));
}
#GET
#Path("/view_mustache")
#UnitOfWork
#Produces(MediaType.TEXT_HTML)
public PersonView getPersonViewMustache(#PathParam("personId") LongParam personId) {
return new PersonView(PersonView.Template.MUSTACHE, findSafely(personId.get()));
}
private Person findSafely(long personId) {
return peopleDAO.findById(personId).orElseThrow(() -> new NotFoundException("No such user."));
}
I think it's because the resource fails and throws a web application exception and code is actually the http status code.
Try it like this:
Response response = RULE.client().target("http://localhost:" + RULE.getLocalPort() + "/people")
.request()
.post(Entity.entity(person, MediaType.APPLICATION_JSON_TYPE));
assertEquals(200, response.getStatus());
Person newPerson = response.readEntity(Person.class);
....
You may also debug like this:
String responseString = response.readEntity(String.class);
Which will dump you the body of the response.