Three Ways to Validate JSON in Java

(a sample github project with all the examples is given at the end)
1. Validate with jackson (obvious way)
The obvious way to validate json is by mapping to a typed object through serialization (here with the most popular lib for that in java: jackson):
public class MyJsonClassTest {
public static class MyJsonClass {
private String myProperty;
public String getMyProperty() {
return myProperty;
}
}
@Test
public void validateMyJsonClass()
throws JsonProcessingException {
String json =
"{\n"
+ " \"myProperty\": \"a value\"\n"
+ "}";
MyJsonClass myJsonClass = new ObjectMapper()
.readValue(json, MyJsonClass.class);
assertThat(myJsonClass.getMyProperty())
.isEqualTo("a value");
}
}
Pros:
- You know exactly what you manipulate in your code;
- You get intellisense from your IDE;
- You can annotate your properties of your type to modify the behavior.
Cons:
- If your JSON payload is a bit versatile, let’s say you have sometimes this payload:
{
"myProperty": "a value"
}
and sometimes this other version of the same payload come across:
{
"myProperty": [ "a value" ]
}
You can’t map this to a specific type.
2. Well, you have to write code, compile it and deploy it each time your payload type changes, even if what you validate doesn’t. You lose a bit the flexibility of JSON.
2. Validate with json-schema
You can leverage the json-schema standard to avoid such limitations from serialization:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "#MyJsonClassSchema.json",
"type": "object",
"properties": {
"myProperty": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": [
{
"type": "string"
}
]
}
]
}
}
}
As you can see, you can use the oneOf combined schema to specify both possibilities. Then, you can use a json schema validation library like everit-org/json-schema or networknt/json-schema-validator to validate against your actual payload:
@Test
public void validateFromJsonSchema() throws JsonProcessingException {
JsonSchema schema = getJsonSchema("#MyJsonClassSchema.json");
String json =
"{\n"
+ " \"myProperty\": [\"a value\"]\n"
+ "}";
Set<ValidationMessage> errors = schema.validate(new ObjectMapper().readTree(json));
assertThat(errors).isEmpty();
}
This produce no error as expected. We used the networknt validator. Json schema support a wide variety of validation scenarios, take a look…
Pros:
- Versatile (conditionals on subschemas, regular expressions, ranges, etc.);
- Not limited to static types
- The validation can be modified without modifying the code;
Cons:
- If the json-schema spec doesn’t work with your use case, you must pray that the library you use allows augmenting the schema with your custom keywords (I think networknt version is better for that);
- If you don’t pay attention to organize your schema with distinct subschema files with $ref, it can quickly become large and tough to read. But libraries seem picky when times come to handle $ref schemas…
- You will have to work if you want to log detail errors with infos taken at different hierarchy levels in the json.
3. Validate with jpath
Perhaps the most interesting way to validate json is to use the JSONPath syntax:
@Test
public void validateWithJsonPath() {
String json =
"{\n"
+ " \"myProperty\": {\n"
+ " \"myProperty\": {\n"
+ " \"myPropertyValue\": \"a value\"\n"
+ " }\n"
+ " }\n"
+ "}";
List<String> value =
JsonPath.read(json, "$..myProperty.myPropertyValue");
assertThat(value).hasSize(1).first().isEqualTo("a value");
}
JSONPath is a query language for JSON like SQL for databases. The example shown above use the jayway lib implementation.
Pros:
- You don’t have to write anything before starting to validate/manipulate JSON: you can query what you want in your payload, cast it and work with it right away.
- Cross-cutting validations are easy to express: the example above query for myPropertyValue that are directly under a myProperty key only and ask to list all the values respecting this pattern.
Cons:
- JPath is not SQL, you will have to work with an intermediate object sometimes to compensate for the lack of functionalities of the query language;
- Not as versatile as a json-schema: you will need two JSONPaths for two schemas.
Tips for implementing a JSON validator
If you want to build a validator of some JSON payloads, you should write your unit tests the following way:
- Implement a setUp method to initialize a valid json payload for your current validation
- In each of the validations unit tests, introduce invalidity into your valid json instance initialized at step 1.
I find the best way to do that is to leverage the JSONPath inside the unit tests (whatever the validator use it or not):
@Test
public void validateAnInvalidity() {
validJson = JsonPath
.parse(this.validJson)
.delete("$..['myProperty']['myPropertyValue']")
.jsonString();
assertThat(this.validator.validate(validJson))
.isEqualTo("There is no myPropertyValue under myProperty");
}
You can also use the method set to introduce a wrong value, etc.
Conclusion
Unfortunately, Java is the Bazaar and not the Cathedral. In other languages, like C#, you would have all these functionality in just one JSON library, often maintain by a well-known organization/group/company. Despite this fact, I hope you will be able to blend all these libs to achieve your needs. Cheers!
Sample project
If you want a working project with the unit tests above, here is the link:
https://github.com/GuillaumeBlanchet/jsonvalidation