Confirm entry before Jackson in Spring Boot
I have built a REST endpoint using Spring Boot. JSON
sent to the endpoint. Jackson transforms JSON
by giving me an object.
JSON
looks like that:
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-02"
}
Jackson converts JSON
to an object based on this class:
public class ParameterDate {
@NotNull(message = "Parameter Date Unadjusted can not be blank or null")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateUnadjusted;
@NotNull(message = "Parameter Date Adjusted can not be blank or null")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateAdjusted;
private Date parameterDateAdded;
private Date parameterDateChanged;
}
This all works great. The problem I am facing is that I would like to validate the data before Jackson transforms the data. For example, if I send
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-40"
}
Where is parameterDateAdjusted
not a valid date (it does not include a 40-day month). Jackson converts this value to 2017-02-09
. One way to get around this is to have a string-only class name it ParameterDateInput
. Validate each Hibernate Validator object ParameterDateInput
and then copy the object ParameterDateInput
into parameterDate
where each field is of the correct type (dates are of type Date
, not type String
). This doesn't sound like a very elegant solution to me. Is there any other way to solve this problem? How is data typically validated in Spring Boot when sent how JSON
? I like to send a message to the user / client what is wrong with the data being sent.
source to share
There is a way to check dates. setLenient()
method
public static boolean isValidDate(String inDate, String format) {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
try {
dateFormat.parse(inDate.trim());
} catch (ParseException pe) {
return false;
}
return true;
}
Just define your own annotation to check the value
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MyDateFormatCheckValidator.class)
@Documented
public @interface MyDateFormatCheck {
String pattern();
...
and the validator class
public class MyDateFormatCheckValidator implements ConstraintValidator<MyDateFormatCheck, String> {
private MyDateFormatCheck check;
@Override
public void initialize(MyDateFormatCheck constraintAnnotation) {
this.check= constraintAnnotation;
}
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
return isValidDate(object, check.pattern());
}
}
source to share
How about a custom JSON deserializer where you can write the logic you want:
@RestController
public class JacksonCustomDesRestEndpoint {
@RequestMapping(value = "/yourEndPoint", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Object createRole(@RequestBody ParameterDate paramDate) {
return paramDate;
}
}
@JsonDeserialize(using = RoleDeserializer.class)
public class ParameterDate {
// ......
}
public class RoleDeserializer extends JsonDeserializer<ParameterDate> {
@Override
public ParameterDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String parameterDateUnadjusted = node.get("parameterDateUnadjusted").getTextValue();
//Do what you want with the date and set it to object from type ParameterDate and return the object at the end.
//Don't forget to fill all the properties to this object because you do not want to lose data that came from the request.
return something;
}
}
source to share