DEV Community

Cover image for Start using JSON Schema validation with AJV
Clara Dios for One Beyond

Posted on • Updated on

Start using JSON Schema validation with AJV

Why JSON Schema validation?

The main advantage you get from JSON Schema over other validation options is that it's cross-platform. JSON Schema validators can be found for almost every programming language. This means that you can write a JSON Schema, and use it on the frontend and the backend no matter the language.

But wait, what is a JSON Schema?

According to https://json-schema.org/ JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.

Let's start validating something. Imagine we have a collection of emojis:

[
  {
    emojiName: ':smiling_face_with_tear:',
    symbol: '😂',
    polite: true,
    emotionalIntensity: 3,
    meanings: ['someone is laughing to tears']
  },
  {
    emojiName: ':slightly_smiling_face:',
    symbol: '🙂',
    polite: true,
    emotionalIntensity: 2,
    meanings: [
      'someone is happy',
      'someone wants to make a passive aggressive statement'
    ] 
  },
  {
    emojiName: ':face_with_symbols_on_mouth:',
    symbol: '🤬',
    polite: false,
    emotionalIntensity: 4,
    meanings: [ 
       'swearing or being vulgar', 
       'convey an outburst of anger, frustration, or rage'
    ]
  },
  { 
    emojiName: ':gem:',
    symbol: '💎',
    polite: 'false',
    emotionalIntensity: '3',
    meanings: 'It means diamond, wealth, marriage, and jewelry. It is mostly used to suggest marriage engagements, expensiveness, and aristocracy',
    color: 'blue'
   }
];
Enter fullscreen mode Exit fullscreen mode

A schema that would help us to validate the elements of this collection would be:


{
  type: "object",
  required: [ "emojiName", "polite", "emotionalIntensity", "meanings" ]
}

Enter fullscreen mode Exit fullscreen mode

But that's a bit too generic. Let's add more information about each property inside the object:

{
  type: "object",
  required: [ 
    "emojiName", 
    "polite", 
    "emotionalIntensity", 
    "meaning" 
  ],
  properties: {
    emojiName: {
       type:"string"
    },
    polite: {
      type: "boolean",
    },
    meanings: {
      type: "array",
    },
    emotionalIntensity: {
      type: "integer",
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We can go further by adding information about the schema itself, such as a title and a description. We can also limit the number of properties allowed. Additionally, when the property has the type "integer" we can specify a range of numbers.

{
    title: "Emoji - English translator schema",
    description : "Here you can add some info about the schema",
    type: "object",
    required: [ "emojiName", "polite", "meanings", "emotionalIntensity"],
    properties: {
      emojiName: {
        type: "string",
        description: "The emoji's official name"
      },
      polite: {
        type: "boolean",
        description: "If the emoji can be used without fear in a formal context"
      },
      meanings: {
        type: "array",
        description: "The different interpretations of that emoji"
      },
      emotionalIntensity: {
        type: "integer",
        description: "Emotional intensity from 0 - 5",
        minimum: 0,
        maximum: 5
      }
    }
  }

Enter fullscreen mode Exit fullscreen mode

Now, how do we get a validation output using the schema?

Well, first let's choose a validation library. Here you can find different validators for different languages: https://json-schema.org/implementations.html

In this case, we are going to use AJV for node.js: https://ajv.js.org/.

Let's create a simple project. You can find the code here: https://github.com/claradios/json-schema-validation-sample

The structure will be as follows:

project structure

We will need to:

  • add node npm init on the root folder.
  • install the AJV JSON-Schema validation library npm i ajv.
  • inside index.js import it, and create an AJV instance.
/// index.js

const Ajv = require("ajv")
const ajv = new Ajv()
Enter fullscreen mode Exit fullscreen mode

We need to import our collection to validate and the schema we have created for that purpose:

const emoji  = require('./schemas/emoji.js');
const emojiCollection = require('./emojiCollection.js');
Enter fullscreen mode Exit fullscreen mode

And validate as follows:

/// index.js

emojiCollection.forEach( emojiItem => {
  // loop collection elements for validation
  const validation = ajv.validate(emoji, emojiItem);
  validation
    ? console.log(`emoji: ${emojiItem.symbol} is correctly built`)
    : console.log(`emoji: ${emojiItem.symbol} has the following errors: ${JSON.stringify(ajv.errors, null, 2)}`);
});

Enter fullscreen mode Exit fullscreen mode

Notice that AJV will return by default the first error it finds. If we want to get all errors we pass the following when instantiating:

const ajv = new Ajv({ allErrors: true })

Now we are ready to run our code (node index.js or npm start) and see the validation output on a terminal:

Validation logs on terminal

The first three elements of our collection seem to be perfectly fine but the diamond has several problems:

diamond validation logs

Work with errors output and make them human-readable.

Given that the error output for a given element can bring a huge amount of information we may want to reshape what those errors look like, in order to make them easier to read. If so, we can install: npm install ajv-errors to our schema and adjust our imports like this and then add the keyword errorMessageto our schema.

const Ajv = require("ajv").default
const ajv = new Ajv({allErrors: true})
// Ajv option allErrors is required
require("ajv-errors")(ajv /*, {singleError: true} */)
Enter fullscreen mode Exit fullscreen mode

Then we can, for example, create specific messages for each keyword to make them more understandable, or return a single message that communicates the core error in a simpler way.

errorMessage: {
        type: "should be an object", // will not replace internal "type" error for the properties listed above
        required: {
            emojiName: "should have a string property 'emojiName'",
            polite: "should have a boolean property 'polite'",
            meanings: "should have an array of strings property 'meanings'",
            emotionalIntensity: "should have an integer property 'emotionalIntensity'",
            symbol: "should have a string property 'symbol'"
        },
        additionalProperties: "should not have properties other than emojiName, polite, meanings, emotionalIntensity, symbol",
      },
Enter fullscreen mode Exit fullscreen mode

What's next and what can AJV be used for?

As a real-life example, Disco is a backend project I am working on. It is based on microservices architecture that translates XML files into JSON and serves them through an API to be rendered.

During the translation process, the JSON schema validator verifies that the resulting JSON files are valid and consistent to continue through the chain.

  • We have created different instances of AJV. Each instance contains a collection of several schemas.

  • Some schemas are used inside others. That is possible thanks to a $ref property that allows you to create links between them. This also helps us to recursively validate content.

  • We have wrapped the AJV library into our own custom library and published as an NPM package so we can install it in several parts of the chain.

  • We have visually documented our schemas in a React.js + d3.js that renders all the schemas and properties our content should match. This in fact could be the subject of another article.

This is all for now, hope it was useful, cheers!

Top comments (3)

Collapse
 
leandroandrade profile image
Leandro Andrade

Congrats.

For design schemas, I really recommend use fluent-json-schema. It's easly to understand and a great developer experience.

Collapse
 
baumannzone profile image
Jorge Baumann

Super nice info, Dios!

Collapse
 
joycestack profile image
JoyceStack

Really useful thread thank you. I am trying to learn about this stuff and so this was super useful.