What is the best way to validate a json request in Java?

Problem

Validating json fields can be a tedious task. Its schema might be deeply nested, and you want to make sure not to have forgotten all those json key existence checks. Validation constraints can include business-specific checks, not only related to format. And they are usually way more complicated. You want to keep them all in a specific place, the one everybody is well aware of.

All in all, if you take a look at your validation code couple of months later after it was initially written, it can look really scary.

How not to do it

Very often, validation checks are tied to data model: every request field is explicitly mapped to data-storage column and validation rules are put in a single place, which is those columns. Looks like a bliss, huh? It works well for simple domain models, where all requests fall under the CRUD category. Have a user? Great, there is a domain class User, corresponding to user table. CRUD requests have the same fields a User class has.

Things start to look different in more complicated domains. What if a user can be “created” within two different scenarios? The first one is when the user registers herself. Apparently, you don’t want to lose her, so the validation rules are quite loose. I believe you should give her a chance to fill non-critical info some time later.

The second one is when the user is registered by any kind of online-support staff. There is no second chance here, so validation must be way more strict.

It’s inherently tricky to put this kind of contextual validation in a data model, because you have to have a way to distinguish between the two scenarios on data-model level, when it’s already too late.

Solution

There is an alternative though. With Validol library, your validation logic mirrors json structure. Complex validation checks decorate more basic ones. Besides, an entire validation logic represents a single expression. And it encourages you to put validation in a specific scenario – the one being currently validated.

Example

Consider a schema to be validated:

{
   "where":{
      "building":1,
      "street":"Red Square"
   }
}

Validation logic reflects the structure of json schema. All the constraints are described right in the structure itself. All the mundane checks like json key existence are already taken care of.

Here it goes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new FastFail<>(
    new IsJsonObject(
        new WellFormedJson(
            new IndexedValue("where", jsonString)
        )
    ),
    whereJsonElement ->
        new NamedBlocOfNameds<>(
            "where",
            List.of(
                new AsString(
                    new Required(
                        new IndexedValue("street", whereJsonElement)
                    )
                ),
                new AsInteger(
                    new Required(
                        new IndexedValue("building", whereJsonElement)
                    )
                )
            ),
            Where.class
        )
)
    .result();

There is no familiar spaghetti-code here. Only pure gluten-free declarative expressions. Let’s take a look what’s going on here, line by line.

Line 1: The whole validation is a fast fail thing, the one returning an error if the first argument results in an error.
Line 4: THe first argument is a declaration of a where block.
Line 3: It must be a well-formed json.
Line 2: Besides, it should be a json object.
Line 7: The second argument is a closure. It’s first argument is a where json object.
Line 8: Here goes the named block of named elements.
Line 9: Its name is where.
Line 10: The second argument is a list of all elements.
Line 13: The first element is street.
Line 12: It’s required.
Line 11: And should be represented as string.
Line 18: The second one is building.
Line 17: It’s required as well.
Line 16: And should be represented as an integer.
Line 22: If all previous checks are successful, an Where object is created.
It’s first argument is street, which must be a String; the second one is building, which must be an integer.

Also, take a look at quick-start section for more examples and line-by-line code analysis.