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'
}
];
A schema that would help us to validate the elements of this collection would be:
{
type: "object",
required: [ "emojiName", "polite", "emotionalIntensity", "meanings" ]
}
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",
}
}
}
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
}
}
}
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:
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 anAJV
instance.
/// index.js
const Ajv = require("ajv")
const ajv = new Ajv()
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');
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)}`);
});
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:
The first three elements of our collection seem to be perfectly fine but the diamond has several problems:
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 errorMessage
to our schema.
const Ajv = require("ajv").default
const ajv = new Ajv({allErrors: true})
// Ajv option allErrors is required
require("ajv-errors")(ajv /*, {singleError: true} */)
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",
},
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)
Congrats.
For design schemas, I really recommend use fluent-json-schema. It's easly to understand and a great developer experience.
Super nice info, Dios!
Really useful thread thank you. I am trying to learn about this stuff and so this was super useful.