26

I have a springboot rest service. The user passes in a json object that gets deserialized into this java pojo:

public final class Request {
    private String id;
    private double code;
    private String name;

    public String getId() {
        return id;
    }

    public double getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

So the user needs to pass in the following json:

{
    "id": “123457896”,
    "code": "Foo",
    "name": "test"
} 

I want to make all of those fields required. Providing anything less or more will throw an exception. Is there a way to tell jackson to validate the input when it deserializes? I've tried @JsonProperty(required=true) but this doesn't work; apparently from here and here it seems the JsonProperty annotation is not respected.

I have this validator that I call in my controller:

@Component
public class RequestValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }

    @Override
    public void validate(Object target, Errors errors) {
        String id = ((Request) target).getId();
        if(id == null || id.isEmpty()) {
            throw new InvalidRequestException("A valid id is missing. Please provide a non-empty or non-null id.");
        }
    }
}

But that just seems tedious and ugly to check every field. So given I'm using java 8, spring boot and latest version of jackson what is the best practice in terms of validating an incoming json input? Or am I already doing it in the most up to date manner?

Richard
  • 5,840
  • 36
  • 123
  • 208

4 Answers4

23

There is no need for custom validator. There is a way to tell jackson to throw

  • JsonMappingException if you don't have required fields

  • UnrecognizedPropertyException if you have extra fields (UnrecognizedPropertyException is just extended JsonMappingException) .

You just need to add @JsonCreator or custom constructor. Something like this should work:

public Request(@JsonProperty(value= "id", required = true)String id,
               @JsonProperty(value= "code",required = true)double code,
               @JsonProperty(value= "name",required = true)String name) {
    this.id = id;
    this.code = code;
    this.name = name;
}

Full Demo:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Main {

public static void main(String[] args) throws IOException {
    test("{\"id\": \"123457896\",\"code\": 1,\"name\": \"test\"}");
    test("{\"id\": \"123457896\",\"name\": \"test\"}");
    test("{\"id\": \"123457896\",\"code\": 1, \"c\": 1,\"name\": \"test\"}");
}

public static void test(String json) throws IOException{
    ObjectMapper mapper = new ObjectMapper();
    try {
        Request deserialized = mapper.readValue(json, Request.class);
        System.out.println(deserialized);
        String serialized = mapper.writeValueAsString(deserialized);
        System.out.println(serialized);
    } catch (JsonMappingException e) {
        System.out.println(e.getMessage());
    }
}

public static class Request {
    private String id;
    private double code;
    private String name;

    public Request(@JsonProperty(value= "id", required = true)String id,
                   @JsonProperty(value= "code",required = true)double code,
                   @JsonProperty(value= "name",required = true)String name) {
        this.id = id;
        this.code = code;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getCode() {
        return code;
    }

    public void setCode(double code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Request{" +
                "id='" + id + '\'' +
                ", code=" + code +
                ", name='" + name + '\'' +
                '}';
    }
}
}

Result:

Request{id='123457896', code=1.0, name='test'}
{"id":"123457896","code":1.0,"name":"test"}
Missing required creator property 'code' (index 1)
 at [Source: {"id": "123457896","name": "test"}; line: 1, column: 34]
Unrecognized field "c" (class Main7$Request), not marked as ignorable (3 known properties: "id", "code", "name"])
 at [Source: {"id": "123457896","code": 1, "c": 1,"name": "test"}; line: 1, column: 53] (through reference chain: Request["c"])
varren
  • 14,551
  • 2
  • 41
  • 72
  • 3
    how about if I want to make sure that code is not empty or null. – M-sAnNan Jul 06 '17 at 10:02
  • E.g test("{\"id\": \"123457896\",\"code\": 1,\"name\": \" "}") for this I want my test to fail because name is empty but field is present – M-sAnNan Jul 06 '17 at 10:03
16

You used Spring Validator approach. There is another approach:

J2EE JSR-303/JSR-349 Bean Validation API. it provides validation annotations (javax, not jackson).

See good example of both here

Dennis R
  • 1,769
  • 12
  • 13
  • with this technique, how do I throw my own custom exceptions like I did above when the validation fails? – Richard Apr 01 '16 at 13:56
  • 1
    You can use @ExceptionHandler annotation for thrown exception and provide the response you want. Something like that: @ResponseStatus(value = HttpStatus.BAD_REQUEST) @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class, HttpRequestMethodNotSupportedException.class}) public ResponseEntity badRequest(HttpServletRequest request, Exception ex) { – Dennis R Apr 01 '16 at 14:00
  • How can i get the exact field name that is required not present in the Json? – Sahil Paudel Aug 13 '19 at 10:43
  • Found this to be the best strategy out of all mentioned – somshivam Sep 25 '19 at 13:33
4

Using the @JsonCreator annotation on the constructor together with @JsonProperty will work. Check this answer as well.

public final class Request {
    private String id;
    private double code;
    private String name;

    @JsonCreator
    public Request(@JsonProperty(required = true) String id,
                   @JsonProperty(required = true) double code,
                   @JsonProperty(required = true) String name) {
        this.id = id;
        this.code = code;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public double getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}
Ferschae Naej
  • 1,826
  • 1
  • 15
  • 16
octavian
  • 686
  • 6
  • 10
2

You can use Jackson schema validation. Create a schema.json and validate each incoming input against this schema. Follow this link for more details http://wilddiary.com/validate-json-against-schema-in-java/

tyagi
  • 265
  • 2
  • 10