31

Our REST API receives some JSON objects input where some fields are required to be not null. Those can be either String/Integer or even might be some other class instance as reference.

We are trying to find a way to enforce those field to be not null, instead of the correct way for null check in the API. Current:

if (myObject.getSomeOtherObject() == null)
    throw new SomeException();

What we want to have is something like:

class MyObject{
    @Required
    OtherObject someOtherObject;
    // ...
}

We have tried 3 things:

1) Upgrade to jackson 2.0.6 and use annotation com.fasterxml.jackson.annotation.JsonProperty But, this just looks not working. Found those references: http://jira.codehaus.org/browse/JACKSON-767

2) Extending JsonDeserializer to check null but the problem is that it does not even executed on the null input.

public class NotNullDeserializer<T> extends JsonDeserializer<T> {

    @Override
    public T deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {

        ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
        Class<T> type = (Class<T>) superClass.getActualTypeArguments()[0];

        T t = jsonparser.readValueAs(type);

        if (t == null){
            String classNameField = type.getName();
            String field = jsonparser.getCurrentName();
            throw new WrongInputException("The field '"+field+"' of type '"+classNameField+"' should not be null.");
        }

        return t;
    }
}

public class NotNullAddressDeserializer extends NotNullDeserializer<Address> {

}

@JsonDeserialize(using=NotNullAddressDeserializer.class)
    Address to;

3) Writing our own @Required annotation and trying to check with ResourceFilter, but it seems I cannot get the actual object from the ContainerRequest and even if we could, not sure how to execute deep check of null reference in object.otherObject.someObject.fieldNotNullable

private class Filter implements ResourceFilter, ContainerRequestFilter {
    private final ArrayList<String> requiredParameters;

    protected Filter() {
        requiredParameters = null;
    }

    protected Filter(ArrayList<String> requiredParameters) {
        this.requiredParameters = requiredParameters;
    }

    @Override
    public ContainerRequestFilter getRequestFilter() {
        return this;
    }

    @Override
    public ContainerResponseFilter getResponseFilter() {
        return null;
    }


    @Override
    public ContainerRequest filter(ContainerRequest request) {
        if (requiredParameters != null && requiredParameters.size() > 0) {
            MultivaluedMap<String, String> params = request.getQueryParameters();
            params.putAll(request.getFormParameters());
            StringBuffer missingParams = new StringBuffer();
            for (String reqParam : requiredParameters) {
                List<String> paramValues = params.get(reqParam);
                if (paramValues == null || paramValues != null && paramValues.size() == 0)
                    missingParams.append(reqParam + ",");
            }
            if (missingParams.length() > 0)
                throw new WrongInputException("Required parameters are missing: " + missingParams);
        }
        return request;
    }
}
kryger
  • 11,746
  • 8
  • 41
  • 60
urir
  • 1,860
  • 3
  • 19
  • 39

4 Answers4

32

JAX-RS separates quite nicely the deserialization from the validation, i.e. JSON-B (or Jackson) has by design no mechanism to enforce values to be non-null, etc. Instead, you can use BeanValidation for that:

  1. Add a dependency to javax.validation:validation-api in provided scope.
  2. Add the javax.validation.constraints.NotNull annotation to your field.

For more details, go here.

rü-
  • 1,657
  • 11
  • 29
2

@Required is a Spring framework annotation for injected beans, so I'd say don't use it for this purpose.

You can use this one instead:

http://robaustin.wikidot.com/annotations-and-notnull

@NotNull String myString;

For runtime checks, try http://code.google.com/p/notnullcheckweaver/

Adam Adamaszek
  • 3,644
  • 1
  • 17
  • 24
  • `@Required` I referenced was our own annotation. Anyway, thanks and I will try your advice now, just - which jar I need for `core.validation.*` imports? – urir Oct 10 '12 at 07:00
  • The annotation in the link is for `this annotation is intended to be used by static analysis tools`. But I need this for strict API validation in runtime. Thanks :) – urir Oct 10 '12 at 07:08
  • The `notnullcheckweaver` is good only for method parameters, while (as explained above) we want also to get deeper. For example, not nullable input object x that has several fields where one of them is field y that is also not nullable and deeper. Thanks – urir Oct 10 '12 at 07:53
2

You can use JSON-SCHEMA as you can express many constraints on JSON fields with it: http://json-schema.org/

Then you can generate from the schema your java classes with @NotNull JSR 303 annotations and use bean validation on your object. It works with Jackson natively, so you should not have any problem.

For instance, you can use the maven plugin to do so: http://wiki.jsonschema2pojo.googlecode.com/git/site/0.3.7/generate-mojo.html

zenbeni
  • 6,098
  • 3
  • 27
  • 51
1

You can enforce not null validation using a combination of the Jackson JSON library and the javax.validation together with the Hibernate validator package.

If you are using Maven these are the dependencies you can use:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>${jackson-version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>${jackson-version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson-version}</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.1.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>${hibernate-validator.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator-annotation-processor</artifactId>
        <version>${hibernate-validator.version}</version>
    </dependency>

    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>3.0.0</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>2.2.6</version>
    </dependency>

</dependencies>

Then your code will have to convert some JSON into your annotated object and you will need to validate the object using javax.validation.Validator. Here is some sample code demonstrating how this can be done (see the relevant validate method):

public class ShareLocationService {

    private ObjectMapper mapper = new ObjectMapper();

    private ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

    // Materialize the Java object which contains the validation annotations
    public ShareLocation readFrom(String json) throws IOException {
        return mapper.readerFor(ShareLocation.class).readValue(json);
    }

    // validate and collect the set of validations
    public Set<String> validate(String json) throws IOException {
        ShareLocation shareMyLocation = readFrom(json);
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<ShareLocation>> violations = validator.validate(shareMyLocation);
        return violations.stream().map(Object::toString).collect(Collectors.toSet());
    }
}

Here is a sample class using the validation annotations:

public class ShareLocation {
    @JsonProperty("Source")
    @NotNull
    private String source;
    @JsonProperty("CompanyCode")
    private String companyCode;
    @JsonProperty("FirstName")
    private String firstName;
    @JsonProperty("LastName")
    private String lastName;
    @JsonProperty("Email")
    private String email;
    @JsonProperty("MobileNumber")
    private String mobileNumber;
    @JsonProperty("Latitude")
    @NotNull
    private Double latitude;
    @JsonProperty("Longitude")
    @NotNull
    private Double longitude;
    @JsonProperty("LocationDateTime")
    @NotNull
    private String locationDateTime;
gil.fernandes
  • 9,585
  • 3
  • 41
  • 57