json validator in Java - using javax.validation.constraints - java

I'm using javax.validation.constraints and have already checked the package usage but still can't find what I'd like to do.
https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html
Here are the two of the variables being sent from the request body
#NotNull
#PositiveOrZero
#Digits(integer = 9, fraction = 0)
private BigDecimal valueA;
#NotNull
#PositiveOrZero
#Digits(integer = 9, fraction = 0)
private BigDecimal valueB;
is it possible to restrict valueB to be not more than 50% of valueA by annotation only? (valueB <= valueA/2)

there are 2 approach to do that:
you can insert #AssertTrue method to validate it
#AssertTrue
public boolean isFiftyPercent(){
//your logic to compare value a and value b
}
or you can make your own annotation validation for global setting. see here: https://www.baeldung.com/spring-mvc-custom-validator

what you are looking for is using Cross-Parameter Constraints. some basic guide can be found here chapter 2.x
https://www.baeldung.com/javax-validation-method-constraints

You need to have a class level annotation for this. Field level annotations only access value of the fields.
Here is an example:
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
// Custom annotation
#Target({ TYPE })
#Retention(RUNTIME)
#Repeatable(NotGreaterThans.class)
#Documented
#Constraint(validatedBy = { NotGreaterThanValidator.class }) // Explicitly define validator
public #interface NotGreaterThan {
String source();
String target();
double percentage()
String message() default "Default message ..";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ TYPE })
#Retention(RUNTIME)
#Documented
#interface NotGreaterThans {
NotGreaterThan[] value();
}
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
// Validator accesses both annotation and object value
public class NotGreaterThanValidator implements ConstraintValidator<NotGreaterThan, Object> {
private String source;
private String target;
private double percentage;
#Override
public void initialize(NotGreaterThan notGreaterThan) {
this.source = notGreaterThan.source();
this.target = notGreaterThan.target();
this.percentage = notGreaterThan.percentage();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BigDecimal sourceValue = customMethodToGetFieldValue(source, value);
BigDecimal targetValue = customMethodToGetFieldValue(target, value);
return source.compareTo(target.multiply(percentage)) <= 0;
}
private BigDecimal customMethodToGetFieldValue(String fieldName, Object object) {
return ....
}
}
// Define your annotation on type
#NotGreaterThan(source ="a", target="b", percentage =50.0)
public class MyCustomBodyClass {
private BigDecimal a;
private BigDecimal b;
}
I haven't tested this, but should give you a head start.

Related

Map elements from "stream in stream" to Set

I'm new to Java streams.
I have an Array of n classes.
The classes have several fields with a particular annotation (SomeAnnotationClass.class)
I'm trying to get a Set of all the fields annotations values which are annotated with this particular annotation. If the field does not have the annotation I want the name of the field.
So i tried something like this:
Stream.of(clazzes).map( c ->
Stream.of((c.getDeclaredFields()))
.map(
field ->
Optional.ofNullable(
field.getDeclaredAnnotation(SomeAnnotationClass.class).value())
.orElse(field.getName())).collect(Collectors.toSet())).collect(Collectors.toSet());
2 issues with this:
I get a Set<Set> instead of Set due to collecting 2 times.
I get a Nullpointer if the annotation is not present but SomeAnnotationClass.class.value() is called
Can I achieve this elegantly with streams?
A set of sets should be flattened:
// in Main.java
public static Set<String> getValuesOrNames(Class ... clazzes) {
return Arrays.stream(clazzes) // convert array to Stream<Class>
.flatMap(c -> Arrays.stream(c.getDeclaredFields())) // convert array of fields Stream<Field>
.map(field -> Optional.ofNullable(field.getAnnotation(SomeAnnotationClass.class))
.map(SomeAnnotationClass::value) // assuming SomeAnnotationClass has value method
.orElse(field.getName())
)
.collect(Collectors.toSet());
}
Test
// annotation class
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface SomeAnnotationClass {
String value() default "";
}
import java.util.*;
import java.util.stream.Collectors;
import lombok.Data;
public class Main {
public static void main(String[] args) {
System.out.println(getValuesOrNames(Something.class, Main.class));
}
#Data
public static class Something {
#SomeAnnotationClass(value = "String foo")
private String foo;
#SomeAnnotationClass
private String emptyFoo;
private String bar;
#SomeAnnotationClass(value = "int id")
private int id;
}
}
Output
[, String foo, bar, int id]
As mentioned by #Andy Turner, you can use to flatMap to map multiple streams into single stream and to avoid NPE check the annotation before accessing value()
Set<String> value = clazzes.stream().map(c -> Stream.of((c.getDeclaredFields()))
.map(field -> Optional.ofNullable(
field.getDeclaredAnnotation(SomeAnnotationClass.class)).map(SomeAnnotationClass::value).orElseGet(field::getName)).collect(Collectors.toSet()))
.flatMap(Collection::stream).collect(Collectors.toSet());
package io.falcon.instagram.indexer.util;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.Enumerated;
import javax.persistence.Id;
public class Example {
public static void main(String[] args) {
List<Class<?>> classes = List.of(Test.class);
Class<Enumerated> someAnnotationClass = Enumerated.class;
Set<String> fieldNames =
classes.stream()
.flatMap(c -> Arrays.stream(c.getDeclaredFields().clone())) // because getDeclaredFields returns array type
.map((Field field) -> Optional.ofNullable(field.getDeclaredAnnotation(someAnnotationClass)).map(a -> field.getName()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
System.out.println(fieldNames);
}
public static class Test {
#Id
private final String id;
#Id
#Enumerated
private final String field;
#Enumerated
private final String another;
#Enumerated
private final String theGame;
public Test(String id, String field, String another, String theGame) {
this.id = id;
this.field = field;
this.another = another;
this.theGame = theGame;
}
}
}

Spring parse String to enum before entering the controller

I have the following controller:
public interface SaveController {
#PostMapping(value = "/save")
#ResponseStatus(code = HttpStatus.CREATED)
void save(#RequestBody #Valid SaveRequest saveRequest);
}
SaveRequest corresponds to:
public class SaveRequest {
#NotNull
private SaveType type;
private String name;
}
and SaveType:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
}
The controller does not receive the enum itself, but a camelCase String. I need to convert that String into the corresponding enum. For instance:
daily should become DAILY.
weekly should become WEEKLY.
monthly should become MONTHLY.
Any other String should become null.
I've tried using the Spring Converter class, which does not work when the enum is inside an object (at least I don't know how to make it work in such times).
I honestly don't know what else to try
https://www.baeldung.com/jackson-serialize-enums
This site should probably give you plenty of options.
Best is probably something like this:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
#JsonCreator
public static SaveType saveTypeforValue(String value) {
return SaveType.valueOf(value.toUpperCase());
}
}
What you require is to have custom annotation with a custom validation class for Enum.
javax.validation library doesn't have inbuilt support for enums.
Validation class
public class SaveTypeSubSetValidator implements ConstraintValidator<SaveTypeSubset, SaveType> {
private SaveType[] subset;
#Override
public void initialize(SaveTypeSubset constraint) {
this.subset = constraint.anyOf();
}
#Override
public boolean isValid(SaveType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset).contains(value);
}
}
interface for validation annotation with validation message
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = SaveTypeSubSetValidator.class)
public #interface SaveTypeSubset {
SaveType[] anyOf();
String message() default "must be any of {anyOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Usage
#SaveTypeSubset(anyOf = {SaveType.NEW, SaveType.OLD})
private SaveType SaveType;
This is one way. More ways are mentioned in this article.

Validating size for BigDecimal for given length, not precision

I have a requirement to validate length for a BigDecimal object using JSR validators. It should contain at max of 10 characters.
Some valid examples:
123456789.0
12345.67890
12345.67
1.2345
Invalid examples:
123456789.0123
123.32131232
How can I achieve this using annotation ? Following #Size annotation is for String objects as per JSR documentation.
#Size(max = 10)
#Column(name = "totalPrice")
private BigDecimal totalPrice;
Custom constraint is needed. That can be done roughly as follows:
Annotation:
#Target({ METHOD, FIELD })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { BigDecimalLengthValidator.class})
public #interface BigDecimalLength {
int maxLength();
String message() default "Length must be less or equal to {maxLength}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
ConstraintValidator:
public class BigDecimalLengthValidator implements ConstraintValidator<BigDecimalLength, BigDecimal> {
private int max;
#Override
public boolean isValid(BigDecimal value, ConstraintValidatorContext context) {
return value == null || value.toString().length() <= max;
}
#Override
public void initialize(BigDecimalLength constraintAnnotation) {
this.max = constraintAnnotation.maxLength();
}
}
Usage:
#BigDecimalLength(maxLength = 3)
private BigDecimal totalPrice;
That should fill basic needs, for further tuning (messages in properties files, etc.) please check Creating custom constraints.
you can try
'#Digits(integer=,fraction=) or #DecimalMax(value = "9999999999.999", message = "The decimal value can not be more than 9999999999.999")'
this both should work.
if you want to know how to use these, then go with following urls
for #digit
https://www.owasp.org/index.php/Bean_Validation_Cheat_Sheet
for #decimalmax
http://www.c-sharpcorner.com/UploadFile/5fd9bd/javax-annotation-and-hibernate-validator-a-pragmatic-appro/

Java annotation for Null but neither Empty nor Blank

Is there are any java annotation(s) that can validate like the example below?
String test;
test = null; //valid
test = ""; //invalid
test = " "; //invalid
test = "Some values"; //valid
You need to create a custom annotation: #NullOrNotBlank
First create the custom annotation: NullOrNotBlank.java
#Target( {ElementType.FIELD})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NullOrNotBlankValidator.class)
public #interface NullOrNotBlank {
String message() default "{javax.validation.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
Then the actual validator: NullOrNotBlankValidator.java
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, String> {
public void initialize(NullOrNotBlank parameters) {
// Nothing to do here
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return value == null || value.trim().length() > 0;
}
}
There isn't such an annotation in either javax.validation or Hibernate Validator. There was a request to add one to Hibernate Validator but it was closed as "won't fix" due to the possibility of writing your own relatively easily. The suggest solution was to either use your own annotation type defined like this:
#ConstraintComposition(OR)
#Null
#NotBlank
#ReportAsSingleViolation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface NullOrNotBlank {
String message() default "{org.hibernate.validator.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
or to use the #Pattern annotation with a regular expression that requires a non-whitespace character to be present (as the Pattern annotation accepts nulls and does not match them against the pattern).
Where is a nice javax.validation.constraints.Pattern annotation.
You can annotate the field with:
#Pattern(regexp = "^(?!\\s*$).+", message = "must not be blank")
This checks if field matches regex. The regex itself is something but not blank (see details here). It uses negative lookahead.
This is possible without creating a custom annotation, by using javax.validation.constraints.Size
// Null values are considered valid
#Size(min=1) String test;
The best way is to create your own constraint validator,
//custom annotation
#Documented
#Constraint(validatedBy = CustomCheck.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomConstarint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//validation logic goes here
public class CustomCheck implements
ConstraintValidator<CustomConstarint, String> {
#Override
public void initialize(CustomConstarint customConstarint) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
//write your logic to validate the field
}
}
Did you try Hibernate-Validator? I think that's what you are looking for.
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
public class MyModel {
#NotNull
private String str1;
#NotEmpty
private String str2;
#NotBlank
private String str3;
}

How to apply hibernate validation to Character type?

#Pattern(regexp = "^[M|F]{1}$", message ="Must be M or F")
private Character gender;
Result:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Character.
How can achieve the following:
apply hibernate validation to the character using a regex pattern
restricting the return type to be a single letter (that's why I chose char)
to everything of this in a single method?
I had similiar problem and i didnt find any default hibernate validator annotation for that. But there is easy way to create custom annotation. (look here https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/validator-customconstraints.html) Below example with sex:
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = SexValidator.class)
#Documented
public #interface Sex
{
String message() default "{customValidator.sex";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ FIELD, METHOD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
#interface List
{
Sex[] value();
}
}
public class SexValidator implements ConstraintValidator<Sex, Character> {
public void initialize(Sex sex)
{
// used only if your annotation has attributes
}
public boolean isValid(Character sex, ConstraintValidatorContext constraintContext)
{
// Bean Validation specification recommends to consider null values as
// being valid. If null is not a valid value for an element, it should
// be annotated with #NotNull explicitly.
if (sex == null)
{
return true;
}
if (sex.equals('F') || sex.equals('M'))
return true;
else
{
return false;
}
}
}
#Column(name = "sex", columnDefinition = "char(1)")
#NotNull
#Sex
private Character sex;

Categories