I am creating a shared component for Request Date constraints, Begin Date is before End Date.
I want to take my current Validation request, and make it common, so I type in the (Begin and EndDate class members for any Class), and it will work. How can this be done? I use annotations above the request class, in ProductRequest below .
Note: How do I set Start and End date parameters in the annotation; they may not always be "Start/End" field members, sometimes they could be "Begin/Finish" in another class .
#DatesRequestConstraint
public class ProductRequest {
private Long productId;
private DateTime startDate;
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
public #interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, ProductRequest> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ProductRequest productRequest, ConstraintValidatorContext constraintValidatorContext) {
if (productRequest.getStartDate() != null &&
productRequest.getEndDate() != null &&
productRequest.getStartDate().isAfter(productRequest.getEndDate())) {
return false;
}
else return true;
}
You can:
Implement ConstraintValidator<DatesMatch, Object> so that you can apply the #DatesMatch annotation on any type;
Add custom String fields to the #DatesMatch annotation where you can specify the names of the fields you want to validate;
Use reflection at runtime to access the field values by their specified name.
There's a similar example of class-level validation over multiple custom fields here: Baeldung: Spring MVC Custom Validation (scroll down to "9. Custom Class Level Validation").
Customized to your example, something like this should work:
#Constraint(validatedBy = DatesMatchValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "The dates don't match.";
String startField();
String endField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
DatesMatch[] value();
}
}
// Accept a list of items so that you can validate more than one pair of dates on the same object if needed
#DatesMatch.List({
#DatesMatch(
startField = "startDate",
endField = "endDate",
message = "The end date must be after the start date."
)
})
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class DatesMatchValidator implements ConstraintValidator<DatesMatch, Object> {
private String startField;
private String endField;
public void initialize(DatesMatch constraintAnnotation) {
this.startField = constraintAnnotation.startField();
this.endField = constraintAnnotation.endField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Instant startFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(startField);
Instant endFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(endField);
if (startFieldValue == null || endFieldValue == null) {
return true;
}
return endFieldValue.isAfter(startFieldValue);
}
}
Update: (in response to comment):
this answer is great, allows multiple pair of dates, however isn't type-string safe, person can type in whatever for the fields in the product fields
Implementing ConstraintValidator<DatesMatch, Object> is meant as an easy catch-all solution you can apply to any class.
But you can absolutely do it in a more type-safe way by implementing a separate ConstraintValidator for each type you want to validate (i.e. ConstraintValidator<DatesMatch, ProductRequest>, ConstraintValidator<DatesMatch, AnotherRequest>, ...) and then specify all of them in the #Constraint(validatedBy={...}) attribute:
#Constraint(validatedBy = {ProductRequestDatesMatchValidator.class, AnotherRequestDatesMatchValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "Invalid dates request.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#DatesMatch(message = "Start and end dates do not match!")
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
#DatesMatch(message = "Begin and finish dates do not match!")
public class AnotherRequest {
private Long productId;
private Instant beginDate;
private Instant finishDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class ProductRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, ProductRequest> {
#Override
public boolean isValid(ProductRequest value, ConstraintValidatorContext context) {
// No need to cast here
Instant startDate = value.getStartDate();
Instant endDate = value.getEndDate();
// You could reuse this logic between each implementation by putting it in a parent class or a utility method
if (startDate == null || endDate == null) {
return true;
}
return startDate.isBefore(endDate);
}
}
public class AnotherRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, AnotherRequest> {
#Override
public boolean isValid(AnotherRequest value, ConstraintValidatorContext context) {
Instant beginDate = value.getBeginDate();
Instant finishDate = value.getFinishDate();
if (beginDate == null || finishDate == null) {
return true;
}
return beginDate.isBefore(finishDate);
}
}
Do note, however, that this is still not compile-time type-safe, as you could put the #DatesMatch annotation on a class for which you haven't written an implementation and the validation will only fail at runtime.
(You could achieve compile-time type-safety using annotation processing, but this another topic for another time.)
You can annotate startDate and endDate with custom annotations something like:
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime endDate;
Then in your isValid(), you can access both startDate and endDate fields by their annotations by iterating over all class fields (in your case, all ProductRequest fields) and checking the following:
field.isAnnotationPresent(StartDateField.class)
field.isAnnotationPresent(EndDateField.class)
The complete code could be as follows:
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ ANNOTATION_TYPE.TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
#interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
#DatesRequestConstraint
class ProductRequest {
private Long productId;
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface StartDateField {
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface EndDateField {
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, Object> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(Object requestObject, ConstraintValidatorContext constraintValidatorContext) {
DateTime startDate = getDateFieldByAnnotation(requestObject, StartDateField.class);
DateTime endDate = getDateFieldByAnnotation(requestObject, EndDateField.class);
if (startDate != null &&
endDate != null &&
startDate.isAfter(endDate)) {
return false;
} else return true;
}
private DateTime getDateFieldByAnnotation(Object requestObject, Class<? extends Annotation> annotationClass) {
return Arrays.stream(requestObject.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(annotationClass)).map(field -> {
try {
return field.get(requestObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}).map(DateTime.class::cast).findAny().orElse(null);
}
}
I have some trouble using cross field validation in Spring Boot. For example there is a class with four fields. The first field is mandatory, all others are optional, but at least one of optional fields must exist.
public class DataContainer {
#NotNull
private String provider;
#Valid
private List<Client> clients;
#Valid
private List<Item> items;
#Valid
private List<Order> orders;
// Getter and setter omitted for simplicity
}
Now I'm looking for a dynamic solution because I need to extend the class easily. How can I do it?
Using Ishikawa Yoshi's hint, I found the solution myself. Here is my implementation for all who are interested.
First I created a new annotation
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {AtLeastOneOfValidator.class})
public #interface AtLeastOneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fields();
int max() default 2147483647;
}
And then the related validator
public class AtLeastOneOfValidator implements ConstraintValidator<AtLeastOneOf, Object> {
private String[] fields;
private int max;
#Override
public void initialize(AtLeastOneOf annotation) {
this.fields = annotation.fields();
this.max = annotation.max();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > this.max) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : this.fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
#SuppressWarnings("rawtypes")
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{" + template + "}").addConstraintViolation();
}
}
Now the annotation can be used
#AtLeastOneOf(fields = {"clients", "items", "orders"})
public class DataContainer {
#NotNull
private String provider;
#Valid
private List<Client> clients;
#Valid
private List<Item> items;
#Valid
private List<Order> orders;
// Getter and setter omitted for simplicity
}
I'm using JAVA 1.6 and Jackson 1.9.9 I've got an enum
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
#JsonValue
final String value() {
return this.value;
}
}
I've added a #JsonValue, this seems to do the job it serializes the object into:
{"event":"forgot password"}
but when I try to deserialize I get a
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names
What am I missing here?
The serializer / deserializer solution pointed out by #xbakesx is an excellent one if you wish to completely decouple your enum class from its JSON representation.
Alternatively, if you prefer a self-contained solution, an implementation based on #JsonCreator and #JsonValue annotations would be more convenient.
So leveraging on the example by #Stanley the following is a complete self-contained solution (Java 6, Jackson 1.9):
public enum DeviceScheduleFormat {
Weekday,
EvenOdd,
Interval;
private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);
static {
namesMap.put("weekday", Weekday);
namesMap.put("even-odd", EvenOdd);
namesMap.put("interval", Interval);
}
#JsonCreator
public static DeviceScheduleFormat forValue(String value) {
return namesMap.get(StringUtils.lowerCase(value));
}
#JsonValue
public String toValue() {
for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
if (entry.getValue() == this)
return entry.getKey();
}
return null; // or fail
}
}
Note that as of this commit in June 2015 (Jackson 2.6.2 and above) you can now simply write:
public enum Event {
#JsonProperty("forgot password")
FORGOT_PASSWORD;
}
The behavior is documented here: https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html
Starting with Jackson 2.6 this annotation may also be used to change serialization of Enum like so:
public enum MyEnum {
#JsonProperty("theFirstValue") THE_FIRST_VALUE,
#JsonProperty("another_value") ANOTHER_VALUE;
}
as an alternative to using JsonValue annotation.
You should create a static factory method which takes single argument and annotate it with #JsonCreator (available since Jackson 1.2)
#JsonCreator
public static Event forValue(String value) { ... }
Read more about JsonCreator annotation here.
Actual Answer:
The default deserializer for enums uses .name() to deserialize, so it's not using the #JsonValue. So as #OldCurmudgeon pointed out, you'd need to pass in {"event": "FORGOT_PASSWORD"} to match the .name() value.
An other option (assuming you want the write and read json values to be the same)...
More Info:
There is (yet) another way to manage the serialization and deserialization process with Jackson. You can specify these annotations to use your own custom serializer and deserializer:
#JsonSerialize(using = MySerializer.class)
#JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
...
}
Then you have to write MySerializer and MyDeserializer which look like this:
MySerializer
public final class MySerializer extends JsonSerializer<MyClass>
{
#Override
public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
// here you'd write data to the stream with gen.write...() methods
}
}
MyDeserializer
public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
// then you'd do something like parser.getInt() or whatever to pull data off the parser
return null;
}
}
Last little bit, particularly for doing this to an enum JsonEnum that serializes with the method getYourValue(), your serializer and deserializer might look like this:
public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
gen.writeString(enumValue.getYourValue());
}
public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
final String jsonValue = parser.getText();
for (final JsonEnum enumValue : JsonEnum.values())
{
if (enumValue.getYourValue().equals(jsonValue))
{
return enumValue;
}
}
return null;
}
I've found a very nice and concise solution, especially useful when you cannot modify enum classes as it was in my case. Then you should provide a custom ObjectMapper with a certain feature enabled. Those features are available since Jackson 1.6. So you only need to write toString() method in your enum.
public class CustomObjectMapper extends ObjectMapper {
#PostConstruct
public void customConfiguration() {
// Uses Enum.toString() for serialization of an Enum
this.enable(WRITE_ENUMS_USING_TO_STRING);
// Uses Enum.toString() for deserialization of an Enum
this.enable(READ_ENUMS_USING_TO_STRING);
}
}
There are more enum-related features available, see here:
https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features
Try this.
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
private Event() {
this.value = this.name();
}
#JsonValue
final String value() {
return this.value;
}
}
I like the accepted answer. However, I would improve it a little (considering that there is now Java higher than version 6 available).
Example:
public enum Operation {
EQUAL("eq"),
NOT_EQUAL("ne"),
LESS_THAN("lt"),
GREATER_THAN("gt");
private final String value;
Operation(String value) {
this.value = value;
}
#JsonValue
public String getValue() {
return value;
}
#JsonCreator
public static Operation forValue(String value) {
return Arrays.stream(Operation.values())
.filter(op -> op.getValue().equals(value))
.findFirst()
.orElseThrow(); // depending on requirements: can be .orElse(null);
}
}
You can customize the deserialization for any attribute.
Declare your deserialize class using the annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) for the attribute that will be processed. If this is an Enum:
#JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;
This way your class will be used to deserialize the attribute. This is a full example:
public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {
#Override
public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
MyEnum type = null;
try{
if(node.get("attr") != null){
type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
if (type != null) {
return type;
}
}
}catch(Exception e){
type = null;
}
return type;
}
}
Here is another example that uses string values instead of a map.
public enum Operator {
EQUAL(new String[]{"=","==","==="}),
NOT_EQUAL(new String[]{"!=","<>"}),
LESS_THAN(new String[]{"<"}),
LESS_THAN_EQUAL(new String[]{"<="}),
GREATER_THAN(new String[]{">"}),
GREATER_THAN_EQUAL(new String[]{">="}),
EXISTS(new String[]{"not null", "exists"}),
NOT_EXISTS(new String[]{"is null", "not exists"}),
MATCH(new String[]{"match"});
private String[] value;
Operator(String[] value) {
this.value = value;
}
#JsonValue
public String toStringOperator(){
return value[0];
}
#JsonCreator
public static Operator fromStringOperator(String stringOperator) {
if(stringOperator != null) {
for(Operator operator : Operator.values()) {
for(String operatorString : operator.value) {
if (stringOperator.equalsIgnoreCase(operatorString)) {
return operator;
}
}
}
}
return null;
}
}
There are various approaches that you can take to accomplish deserialization of a JSON object to an enum. My favorite style is to make an inner class:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;
#JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
MAIN("Main"),
MAIN_DISCOUNT("Main Discount");
private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
static {
ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
.collect(Collectors.toMap(
Enum::name,
Function.identity()));
}
private final String displayName;
FinancialAccountSubAccountType(String displayName) {
this.displayName = displayName;
}
#JsonCreator
public static FinancialAccountSubAccountType fromJson(Request request) {
return ENUM_NAME_MAP.get(request.getCode());
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
private static class Request {
#NotEmpty(message = "Financial account sub-account type code is required")
private final String code;
private final String displayName;
#JsonCreator
private Request(#JsonProperty("code") String code,
#JsonProperty("name") String displayName) {
this.code = code;
this.displayName = displayName;
}
public String getCode() {
return code;
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
}
}
In the context of an enum, using #JsonValue now (since 2.0) works for serialization and deserialization.
According to the jackson-annotations javadoc for #JsonValue:
NOTE: when use for Java enums, one additional feature is that value returned by annotated method is also considered to be the value to deserialize from, not just JSON String to serialize as. This is possible since set of Enum values is constant and it is possible to define mapping, but can not be done in general for POJO types; as such, this is not used for POJO deserialization.
So having the Event enum annotated just as above works (for both serialization and deserialization) with jackson 2.0+.
Besides using #JsonSerialize #JsonDeserialize, you can also use SerializationFeature and DeserializationFeature (jackson binding) in the object mapper.
Such as DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, which give default enum type if the one provided is not defined in the enum class.
In my case, this is what resolved:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {
DAILY(1),
WEEKLY(2),
;
private final int id;
PeriodEnum(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return this.name();
}
#JsonCreator
public static PeriodEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
}
Serializes and deserializes the following json:
{
"id": 2,
"name": "WEEKLY"
}
I hope it helps!
Here, 'value' acts as a deserialiser and 'namespace' acts as a serialiser. Hence, you can pass in value "Student Absent" to API while saving, and in DB it will be saved as "STUDENT_ABSENT". On the other hand, while retrieving data in your class, your API will return "Student Absent"
import com.fasterxml.jackson.annotation.JsonProperty;
public enum AttendanceEnums {
STUDENT_PRESENT,
#JsonProperty(value = "Student Absent", namespace = "Student Absent")
STUDENT_ABSENT;
}
I had been looking for a solution to enum serialization and I finally made a solution.
https://github.com/sirgilligan/EnumerationSerialization
https://digerati-illuminatus.blogspot.com/2022/10/java-enum-generic-serializer-and.html
It uses a new annotation and two new classes, EnumerationSerializer and EnumerationDeserializer. You can subclass the EnumerationDeserializer and make a class that sets the enum Class (typical approach) or you can annotate the enum and you don't have to have a subclass of EnumerationDeserializer.
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
RED,
GREEN,
BLUE
}
Notice how the implementation of ContextualDeserializer pulls the class from the annotation.
https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationDeserializer.java
There is a lot of good code in this that might give insights.
For your specific question you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
//This annotation is optional because the code looks for value or alias.
#EnumJson(serializeProjection = Projection.VALUE)
private final String value;
private Event(final String description) {
this.value = description;
}
}
Or you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
}
That's all you have to do.
Then if you have a class that "has a" event you can annotate each occurance to serialize the way you want.
class EventHolder {
#EnumJson(serializeProjection = Projection.NAME)
Event someEvent;
#EnumJson(serializeProjection = Projection.ORDINAL)
Event someOtherEvent;
#EnumJson(serializeProjection = Projection.VALUE)
Event yetAnotherEvent;
}
The simplest way I found is using #JsonFormat.Shape.OBJECT annotation for the enum.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
....
}
I did it like this :
// Your JSON
{"event":"forgot password"}
// Your class to map
public class LoggingDto {
#JsonProperty(value = "event")
private FooEnum logType;
}
//Your enum
public enum FooEnum {
DATA_LOG ("Dummy 1"),
DATA2_LOG ("Dummy 2"),
DATA3_LOG ("forgot password"),
DATA4_LOG ("Dummy 4"),
DATA5_LOG ("Dummy 5"),
UNKNOWN ("");
private String fullName;
FooEnum(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return fullName;
}
#JsonCreator
public static FooEnum getLogTypeFromFullName(String fullName) {
for (FooEnum logType : FooEnum.values()) {
if (logType.fullName.equals(fullName)) {
return logType;
}
}
return UNKNOWN;
}
}
So the value of the property "logType" for class LoggingDto will be DATA3_LOG
This post is old, but if it can help someone, use JsonFormat.Shape.STRING
#JsonFormat(shape = JsonFormat.Shape.STRING)
public enum SomeEnum{
#JsonProperty("SOME_PROPERTY")
someProperty,
...
}
Code results is like this
{"someenum":"SOME_PROPERTY"}
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum LoginOptionType {
PHONE(1, "Phone"), MAIL(2, "mail"), PERSONAL_EMAIL(3, "Personal email");
private static List<LoginOptionType> all;
static {
all = new ArrayList<LoginOptionType>() {
{
add(LoginOptionType.PHONE);
add(LoginOptionType.MAIL);
add(LoginOptionType.PERSONAL_EMAIL);
}
};
}
private final Integer viewValue;
private final String name;
LoginOptionType(Integer viewValue, String name) {
this.viewValue = viewValue;
this.name = name;
}
public Integer getViewValue() {
return viewValue;
}
public String getName() {
return name;
}
public static List<LoginOptionType> getAll() {
return all;
}
}
Response
[
{
"viewValue": 1,
"name": "Phone"
},
{
"viewValue": 2,
"name": "mail"
},
{
"viewValue": 3,
"name": "Personal email"
}
]
Given a model hierarchy like so:
// WARNING: This is pseudo-code for giving an example!
public abstract class BaseVehicle {
private String make;
private String model;
// Constructors, getters & setters down here
}
public class Motorcycle extends BaseVehicle {
private int numCylinders;
// Constructors, getters & setters down here
}
public class Car extends BaseVehicle {
// ...etc.
}
And given the following payload class (that will be sent to a Spring controller):
public class Payload {
#JsonIgnore
#JsonProperty(value = "orgId")
private String orgId;
#JsonIgnore
#JsonProperty(value = "isInitialized")
private Boolean isInitialized;
#JsonIgnore
#JsonProperty(value = "vehicle")
private BaseVehicle vehicle;
// Constructors, getters & setters down here
}
I'm wondering if its possible to have the Spring controller (using Jackson for JSON serialization) configured to only expect a BaseVehicle instance in the Payload it receives, but to dynamically infer which BaseVehicle subclass was actually sent:
#RequestMapping(value='/payload', method=RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
#ResponseBody MyAppResponse onPayload(#RequestBody Payload payload) {
logger.info("Received a payload with a vehicle of type: " + payload.getVehicle().getClass().getName());
}
So that if I happen to send a Payload JSON that contains a Motorcycle as its vehicle field, then when that logger.info(...) statement fires, the code sees the vehicle is a Motorcycle (and ditto for any other BaseVehicle subclass)?
Is this possible, if so, how?
However I'd greatly prefer a solution that allows the JSON to remain as-is.
As I mentioned in my comment above, you could analyze the payload vehicle JSON object tree in order to make a little analysis trying to detect the payload element type.
#JsonDeserialize(using = BaseVehicleJsonDeserializer.class)
abstract class BaseVehicle {
#JsonProperty
private String make;
#JsonProperty
private String model;
}
#JsonDeserialize(as = Car.class)
final class Car
extends BaseVehicle {
}
#JsonDeserialize(as = Motorcycle.class)
final class Motorcycle
extends BaseVehicle {
#JsonProperty
private int numCylinders;
}
The trick here is the #JsonDeserialize annotation. The BaseVehicleJsonDeserializer can be implemented as follows:
final class BaseVehicleJsonDeserializer
extends JsonDeserializer<BaseVehicle> {
#Override
public BaseVehicle deserialize(final JsonParser parser, final DeserializationContext context)
throws IOException {
final TreeNode treeNode = parser.readValueAsTree();
final Class<? extends BaseVehicle> baseVehicleClass = Stream.of(treeNode)
// Check if the tree node is ObjectNode
.filter(tn -> tn instanceof ObjectNode)
// And cast
.map(tn -> (ObjectNode) tn)
// Now "bind" the object node with if the object node can be supported by the resolver
.flatMap(objectNode -> Stream.of(BaseVehicleTypeResolver.cachedBaseVehicleTypeResolvers).filter(resolver -> resolver.matches(objectNode)))
// If found, just get the detected vehicle class
.map(BaseVehicleTypeResolver::getBaseVehicleClass)
// Take the first resolver only
.findFirst()
// Or throw a JSON parsing exception
.orElseThrow(() -> new JsonParseException(parser, "Cannot parse: " + treeNode));
// Convert the JSON tree to the resolved class instance
final ObjectMapper objectMapper = (ObjectMapper) parser.getCodec();
return objectMapper.treeToValue(treeNode, baseVehicleClass);
}
// Known strategies here
private enum BaseVehicleTypeResolver {
CAR_RESOLVER {
#Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Car.class;
}
#Override
protected boolean matches(final ObjectNode objectNode) {
return !objectNode.has("numCylinders");
}
},
MOTORCYCLE_RESOLVER {
#Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Motorcycle.class;
}
#Override
protected boolean matches(final ObjectNode objectNode) {
return objectNode.has("numCylinders");
}
};
// Enum.values() returns array clones every time it's invoked
private static final BaseVehicleTypeResolver[] cachedBaseVehicleTypeResolvers = BaseVehicleTypeResolver.values();
protected abstract Class<? extends BaseVehicle> getBaseVehicleClass();
protected abstract boolean matches(ObjectNode objectNode);
}
}
As you can see, such an approach is more or less fragile and sophisticated, but it tries to make some analysis. Now, how it can be used:
final ObjectMapper mapper = new ObjectMapper();
Stream.of(
"{\"orgId\":\"foo\",\"isInitialized\":true,\"vehicle\":{\"make\":\"foo\",\"model\":\"foo\"}}",
"{\"orgId\":\"bar\",\"isInitialized\":true,\"vehicle\":{\"make\":\"bar\",\"model\":\"bar\",\"numCylinders\":4}}"
)
.map(json -> {
try {
return mapper.readValue(json, Payload.class);
} catch ( final IOException ex ) {
throw new RuntimeException(ex);
}
})
.map(Payload::getVehicle)
.map(BaseVehicle::getClass)
.forEach(System.out::println);
Output:
class q43138817.Car
class q43138817.Motorcycle
I'm looking to do a little custom validation with JSR-303 javax.validation.
I have a field. And If a certain value is entered into this field I want to require that a few other fields are not null.
I'm trying to figure this out. Not sure exactly what I would call this to help find an explanation.
Any help would be appreciated. I am pretty new to this.
At the moment I'm thinking a Custom Constraint. But I'm not sure how to test the value of the dependent field from within the annotation. Basically I'm not sure how to access the panel object from the annotation.
public class StatusValidator implements ConstraintValidator<NotNull, String> {
#Override
public void initialize(NotNull constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("Canceled".equals(panel.status.getValue())) {
if (value != null) {
return true;
}
} else {
return false;
}
}
}
It's the panel.status.getValue(); giving me trouble.. not sure how to accomplish this.
Define method that must validate to true and put the #AssertTrue annotation on the top of it:
#AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
The method must start with 'is'.
In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.
Use the following code as an idea (I've not test it).
Validator interface
/**
* Validates that field {#code dependFieldName} is not null if
* field {#code fieldName} has value {#code fieldValue}.
**/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
#Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
#Documented
public #interface NotNullIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "{NotNullIfAnotherFieldHasValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Validator implementation
/**
* Implementation of {#link NotNullIfAnotherFieldHasValue} validator.
**/
public class NotNullIfAnotherFieldHasValueValidator
implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
#Override
public void initialize(NotNullIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
String fieldValue = BeanUtils.getProperty(value, fieldName);
String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
.addNode(dependFieldName)
.addConstraintViolation();
return false;
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return true;
}
}
Validator usage example (hibernate-validator >= 6 with Java 8+)
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne")
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Validator usage example (hibernate-validator < 6; the old example)
#NotNullIfAnotherFieldHasValue.List({
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne"),
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
})
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.
See also this great answer: Cross field validation with Hibernate Validator (JSR 303)
You should make use of custom DefaultGroupSequenceProvider<T>:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation {}
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm> {
#Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField())) {
sequence.add(ConditionalValidation.class);
}
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
}
}
MyCustomForm.java
#GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {
private String someField;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
#NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
}
See also related question on this topic.
Here's my take on it, tried to keep it as simple as possible.
The interface:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = OneOfValidator.class)
#Documented
public #interface OneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
Validation implementation:
public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
private String[] fields;
#Override
public void initialize(OneOf annotation) {
this.fields = annotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("{" + template + "}")
.addConstraintViolation();
}
}
Usage:
#OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Messages:
one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
A different approach would be to create a (protected) getter that returns an object containing all dependent fields. Example:
public class MyBean {
protected String status;
protected String name;
#StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator can now access StatusAndSomething.status and StatusAndSomething.something and make a dependent check.
Sample below:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
#SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner {
private final Validator validator;
public SampleJavaXValidation(Validator validator) {
this.validator = validator;
}
public static void main(String[] args) {
SpringApplication.run(SampleJavaXValidation.class, args);
}
#Override
public void run(String... args) throws Exception {
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
}
public enum SampleTypes {
TYPE_A,
TYPE_B;
}
#Valid
public static class SampleDataCls {
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB) {
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
}
public SampleTypes getType() {
return type;
}
public String getValueA() {
return valueA;
}
public String getValueB() {
return valueB;
}
#Pattern(regexp = "TRUE")
public String getConditionalValueA() {
if (type.equals(SampleTypes.TYPE_A)) {
return valueA != null ? "TRUE" : "";
}
return "TRUE";
}
#Pattern(regexp = "TRUE")
public String getConditionalValueB() {
if (type.equals(SampleTypes.TYPE_B)) {
return valueB != null ? "TRUE" : "";
}
return "TRUE";
}
}
}