JSON Schema Part 2 – Automating JSON validation tests

In my first post on the topic of JSON Schema I outlined the limitations of specifying data-formats by example, described how schema languages help address this, and provided an introduction to the not so well known schema language for JSON – JSON Schema.

json-schema.org

In that first article I stated my intention to document non-trivial JSON data-format I specified in the future using JSON Schema in addition to using specification-by-example, whether it be for an API resource or a message payload. When you adhere to this you quickly realise that while producing a JSON Schema is valuable, to increase the value you’ll additionally want to extend the tests for your JSON producing components to automate the following:

  • Test the schema remains valid – i.e. it’s well-formed and conforms to the JSON Schema spec.- in order to avoid publishing a broken or invalid schema.
  • Test your app produces JSON that conforms to the published schema. This goes beyond just asserting that the component produces an expected lump of JSON, to additionally check that the generated JSON conforms to any validation constraints in the schema.

This follow-up, second post on JSON Schema, provides example code for implementing the above such tests using Java, JUnit and an open-source library for JSON Schema Validation.

Example Use Case

To support the code examples that follow I’ll reuse the example from the first blog post.

In this example, we’re responsible for implementing a component that sends a user’s ‘viewing activity’ data to one or more other components. We’ve decided to use messaging to integrate the components, and to use JSON as the data-format to represent the viewing activity. We’re also responsible for documenting the exchanged messages ahead of time to allow the two components to be developed in parallel by different parties. We start by documenting an example of the JSON data-format –

{ 
  "feedbacks": [
    {
      "rating": 3,
      "message": "Would’ve liked more on the topic of Docker.",
      "authored": "2016-06-09T13:43:57Z"
    }
  ],
  "received" : "2016-06-09T13:43:58.123Z"
}

Sensibly, to remove any ambiguity with the above example, we’ve also produced the following JSON Schema to document the data-format.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "User viewing activity",
  "description": "Viewing activity generated by a user.",
  "type": "object",
  "properties": {
    "feedbacks": {
      "description": "One or more items of feedback.",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "rating": {
            "enum": [1, 2, 3, 4, 5]
          },
          "message": {
            "type": "string"
          },
          "authored": {
            "description": "The date/time the feedback was authored. ISO-8601 format datetime string, to second resolution, with 'Z' time zone designator.",
            "type": "string",
            "format": "date-time",
            "pattern": "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
          }
        },
        "required": ["rating"]
      },
      "minItems": 1
    },
    "received": {
      "description": "The date/time the activity was received. ISO-8601 format datetime string, to millisecond resolution, with 'Z' time zone designator.",
      "type": "string",
      "format": "date-time",
      "pattern": "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.[0-9]{3}Z$"
    }
 },
 "required": ["received"]
}

Before publishing the above schema, or completing the implementation of the viewing activity message producer, we want to test that the above JSON schema is valid, and also that the serialised JSON representations of the viewing activity we’re including in the message body adheres to the schema.

Selecting a JSON Schema Validation Library

Extending your automated tests to check that both your JSON Schema and the generated JSON are well-formed, by parsing them, is a good start. But to check your schema conforms to the JSON Schema spec, and your JSON conforms to the schema, requires applying the rules defined in the JSON Schema Validation sub-spec. This is non-trivial to implement and not something I wanted to reinvent if there was a reliable, existing solution for the Java platform.

The first place I thought of looking for a Java implementation of JSON Schema Validation was the Jackson project, given its currently the most popular JSON library for the platform. The Jackson team have indeed done some work in the area of JSON Schema in the past. The Jackson JSON Schema Module automates the generation of JSON Schema. This is potentially useful, but it currently only supports JSON Schema v3, and doesn’t provide JSON Schema Validation support. So we need to look elsewhere.

The first professional looking Java implementation of the JSON Schema Validation spec I discovered was json-schema-validator project. This project has been around for several years (release 1.0, Sept 2012), there have been close to 100 releases in total, and it supports the latest draft (v4) of JSON Schema. It utilises the Jackson library’s in its API, for representing de-serialised JSON instances, including schema. The original author no longer appears to be involved, and the last release was some time ago (10/2014) now, which may not be a good indicator for the project’s future development. But there are plenty of tests, a CI process, and an online demo, so I decided to give it a try. As described below the library proved to work well.

Add the library to your Java project and declare it as compile (and run) time dependency for your tests. If you’re using Gradle this is done by adding the following line to the dependencies block in build.gradle –

dependencies {
  ...
  testCompile('com.github.fge:json-schema-validator:${jsonSchemaValidatorVersion}')
}

where ${jsonSchemaValidatorVersion} should be substituted for the latest available production release.

Validating your JSON Schema

The json-schema-validator library automatically validates a JSON Schema at the point it is used to validate an instance of JSON. However, it’s worth having a separate, dedicated test for validating the schema. This can be implemented as follows.

First you create a JsonSchemaFactory. The default method for creating the factory defaults to validating the latest v4 schema, which is what we need.

this.jsonSchemaFactory = JsonSchemaFactory.byDefault();

The JsonSchemaFactory comprises a SyntaxValidator which provides a couple of methods for validating a previously parsed JSON Schema. The most comprehensive of these methods – validateSchema() – returns a ProcessingReport which in addition to indicating whether the schema was valid, details any validation constraint violations.

When combined with the excellent AssertJ library you can assert whether a JSON Schema is valid, and if it isn’t output a failure message containing each of the violations as follows –

ProcessingReport report = 
  this.jsonSchemaFactory.getSyntaxValidator().validateSchema(schemaJsonNode);

assertThat(report.isSuccess()).withFailMessage(
  "JSON Schema for Viewing Activity [%s] is invalid. Errors [%s].", 
    JSON_SCHEMA_PATH, report).isTrue();

If your JSON Schema is malformed this will be detected when you read (deserialise and parse) it using Jackson, before passing the JsonNode it to validateSchema().

If your JSON Schema is well-formed but contains a field with an invalid value, e.g. a “type” field which doesn’t contain a valid JSON type (array, object, number etc), e.g.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "User viewing activity",
  "description": "Viewing activity generated by a user.",
  "type": "foo",
  ...
}

then the above assert will fail with the following informative error message –

java.lang.AssertionError: JSON Schema for Viewing Activity [/message-schema/viewingActivity.json] is invalid. Errors [com.github.fge.jsonschema.core.report.ListProcessingReport: failure
--- BEGIN MESSAGES ---
error: "foo" is not a valid primitive type (valid values are: [array, boolean, integer, null, number, object, string])
 level: "error"
 domain: "syntax"
 schema: {"loadingURI":"#","pointer":""}
 keyword: "type"
 found: "foo"
 valid: ["array","boolean","integer","null","number","object","string"]
--- END MESSAGES ---
].

Validating an instance of JSON adheres to the Schema

Having written a test to check your JSON Schema remains valid you’ll now want to test that any JSON your components produce adhere to this schema. This can be implemented using the json-schema-validator library as follows.

After reading (deserialising and parsing) your previously validated JSON Schema to a Jackson JsonNode, use the aforementioned JsonSchemaFactory to build a JsonSchema instance –

JsonSchema jsonSchema = 
  this.jsonSchemaFactory.getJsonSchema(readAndDeserialiseJsonSchema());

The JsonSchema class supports validating JSON instances against a specific JSON Schema. A couple of varieties of validation methods are provided. The simplest just returns a flag indicating whether the instance adheres to the schema. There are also methods that return the aforementioned ProcessingReport, which additionally details each of the detected validation errors if the JSON doesn’t conform to the schema. All of the methods require you to supply a deserialsied representation of the JSON in the form of a Jackson JsonNode –

// Validate JSON instance against this schema and return report comprising 
// result & details of any errors (2nd param set to ‘true’ to validate 
// child nodes even if parent container (array, object) are invalid)
ProcessingReport report = jsonSchema.validate(jsonNode, true);

Using AssertJ you can assert whether the JSON conforms to the schema, and if not output a failure message containing each of the violations as follows –

assertThat(report.isSuccess()).withFailMessage("Generated JSON [%s] does 
  not conform to schema [%s]. Schema validation errors [%s]", 
    jsonContent.getJson(), JSON_SCHEMA_PATH, report).isTrue();

If the generated JSON doesn’t conform to the schema, for example because it is missing a field defined as mandatory in the schema, such as the received date/time, the assertion will fail with the following informative error message –

java.lang.AssertionError: Generated JSON [{"feedbacks":[{"rating":3}]}] does not conform to schema [/message-schema/viewingActivity.json]. Schema validation errors [com.github.fge.jsonschema.core.report.ListProcessingReport: failure
--- BEGIN MESSAGES ---
error: object has missing required properties (["received"])
 level: "error"
 schema: {"loadingURI":"#","pointer":""}
 instance: {"pointer":""}
 domain: "validation"
 keyword: "required"
 required: ["received"]
 missing: ["received"]
--- END MESSAGES ---
]

Similarly, if a field, such as the received date/time, doesn’t match a pattern defined in the schema, the assert will fail with the following error message –

java.lang.AssertionError: Generated JSON [{"received":"2016-07-08T19:32:58Z","feedbacks":[{"rating":3}]}] does not conform to schema [/message-schema/viewingActivity.json]. Schema validation errors [com.github.fge.jsonschema.core.report.ListProcessingReport: failure
--- BEGIN MESSAGES ---
error: ECMA 262 regex "^\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}\.[0-9]{3}Z$" does not match input string "2016-07-08T19:32:58Z"
 level: "error"
 schema: {"loadingURI":"#","pointer":"/properties/received"}
 instance: {"pointer":"/received"}
 domain: "validation"
 keyword: "pattern"
 regex: "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.[0-9]{3}Z$"
 string: "2016-07-08T19:32:58Z"
--- END MESSAGES ---
]

Finally, if a field, such as feedback.rating, contains a value outside of a range or enum defined in the schema, then the above assert will fail with the following error message –

java.lang.AssertionError: Generated JSON [{"received":"2016-07-08T19:32:58.123Z","feedbacks":[{"rating":0}]}] does not conform to schema [/message-schema/viewingActivity.json]. Schema validation errors [com.github.fge.jsonschema.core.report.ListProcessingReport: failure
--- BEGIN MESSAGES ---
error: instance value (0) not found in enum (possible values: [1,2,3,4,5])
 level: "error"
 schema: {"loadingURI":"#","pointer":"/properties/feedbacks/items/properties/rating"}
 instance: {"pointer":"/feedbacks/0/rating"}
 domain: "validation"
 keyword: "enum"
 value: 0
 enum: [1,2,3,4,5]
--- END MESSAGES ---
]

And that concludes this tutorial on how to test your JSON Schema is valid, and test that JSON generated by your components conform to a JSON Schema.

Further Reading

JUnit Test Case for JSON Schema Validation

The full code (a JSON schema and JUnit Test Case) from which the example code snippets shown above were taken, is available in the project named json-schema-validator-examples in my Github repo. See the project readme for more details

json-schema-validator library examples

You can find further code examples for using the json-schema-validator library in this location in its Git repo.

I hope this tutorial has been useful.

Thanks.

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s