ChikChak
ChikChak

Reputation: 1034

Spring validate @Value without setter

I have some value from my configuration file, that should be a JSON (which will be loaded as a String).

I'd like Spring to validate that this value is indeed a valid JSON before injecting it and throw an error else.

I'm injecting it as follows:

@Value("${source.SomeJsonString}")
private String someJsonString;

I saw the following: How to make simple property validation when using Spring @Value

However, since I have multiple classes that should be injected with source.SomeJsonString, I wouldn't like to create a setter for each, and write the validation again and again.

Is there any way to write the validator only once?

I thought about creating annotation (Spring validate string value is a JSON), but it seems that values that are annotated with @Value cannot be validated .

Is there any other way?

Upvotes: 3

Views: 2496

Answers (2)

Mafor
Mafor

Reputation: 10711

You could insert the string as a bean, parsing/validating it first:

@Configuration
public class JsonStringPropertyConfig {

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    @Qualifier("someJsonString")
    String someJsonString(@Value("${source.someJsonString}") String someJsonString) throws JsonProcessingException {
        // Validate
        objectMapper.readTree(someJsonString);
        return someJsonString;
    }

}
@Service
public class SomeService {

    @Autowired
    @Qualifier("someJsonString")
    private String someJsonString;

}

or even simpler, with the @Resource annotation:

@Configuration
public class JsonStringPropertyConfig {

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    String someJsonString(@Value("${source.someJsonString}") String someJsonString) throws JsonProcessingException {
        // Validate
        objectMapper.readTree(someJsonString);
        return someJsonString;
    }

}
@Service
public class SomeService {

    @Resource(name = "someJsonString")
    private String someJsonString;

}

Upvotes: -1

Eugene Khyst
Eugene Khyst

Reputation: 10315

Spring externalized configuration can be validated using JSR 303 Bean Validation API. But it requires Spring Type-safe Configuration Properties instead of @Value("${property}").

Add Hibernate Validator dependency to build.gradle

implementation 'org.hibernate.validator:hibernate-validator'

The type-safe configuration properties must be annotated with @Validated and the field someJsonString with a custom annotation @ValidJsonConstraint

@Component
@ConfigurationProperties("source")
@Validated
public class SourceProperties {

  @ValidJsonConstraint
  private String someJsonString;

  public String getSomeJsonString() {
    return someJsonString;
  }

  public void setSomeJsonString(String someJsonString) {
    this.someJsonString = someJsonString;
  }
}

You can inject the properties into all required services, so the validation code is not duplicated

@Autowired
private SourceProperties sourceProperties;

It's time to create the custom annotation

@Documented
@Constraint(validatedBy = ValidJsonValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidJsonConstraint {

  String message() default "Invalid JSON";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};
}

and a validator to validate fields annotated with the custom annotation

public class ValidJsonValidator implements ConstraintValidator<ValidJsonConstraint, String> {

  private final ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public void initialize(ValidJsonConstraint constraintAnnotation) {
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    try {
      objectMapper.readTree(value);
      return true;
    } catch (JsonProcessingException e) {
      return false;
    }
  }
}

When in the application.properties the source.someJsonString value is valid JSON

source.someJsonString={"test":"qwe"}

application successfully starts.

When JSON is invalid

source.someJsonString=qwe

Application fails to start with the following exception

***************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'source' to intellectsoft.afgruppen.shiftschedule.SourceProperties failed:

    Property: source.someJsonString
    Value: qwe
    Origin: class path resource [application.properties]:26:23
    Reason: Invalid JSON


Action:

Update your application's configuration

Also, the same can be achieved a bit easier without JSR 303 Bean Validation API.

Create a custom validation component

@Component
public class JsonValidator {

  private final ObjectMapper objectMapper = new ObjectMapper();

  public boolean isValid(String value) {
    try {
      objectMapper.readTree(value);
      return true;
    } catch (JsonProcessingException e) {
      return false;
    }
  }
}

Inject the validator and perform validation in the property setter

@Component
@ConfigurationProperties("source")
public class SourceProperties {

  private final JsonValidator jsonValidator;

  private String someJsonString;

  public SourceProperties(JsonValidator jsonValidator) {
    this.jsonValidator = jsonValidator;
  }

  public String getSomeJsonString() {
    return someJsonString;
  }

  public void setSomeJsonString(String someJsonString) {
    if (!jsonValidator.isValid(someJsonString)) {
      throw new IllegalArgumentException(someJsonString + " is not a valid JSON");
    }
    this.someJsonString = someJsonString;
  }
}

Upvotes: 5

Related Questions