0.0.2 • Published 3 years ago

@flagpolejs/json-validator v0.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

json-validator

This validator allows you to test whether a given JSON response matches this defined structure.

The format is very similar to the popular JSON Schema https://json-schema.org/ format. However, it could be difficult to read and write manually. For basic formats, we can get away with something easier. This library was created to be small in size, not designing for every possible use case (but covering the most common ones), being legible, and able to be manually written.

Getting Started and the Schema

In its simplest form you can just define an object with properties of the JSON property names and strings with the type.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    isActive: "boolean",
    teams: "array",
  },
};

This would match a JSON body like this:

{
    id: 5,
    firstName: "George",
    lastName: "Brett",
    isActive: false,
    teams: [
        "Kansas City Royals"
    ]
}

Whenever we define a value in our schema as a string, we are defining its type. Technically, we could use a more verbose schema definition for the same thing, like this:

const schema = {
  properties: {
    id: { type: "number" },
    firstName: { type: "string" },
    lastName: { type: "string" },
    isActive: { type: "boolean" },
    teams: { type: "array" },
  },
};

But there really is no point in doing that if we simply want to verify the type.

The above works well for a flat structure, but when we start to get nested with arrays or objects then what? Well the next step is we want to make sure every team listed inside the teams array of our JSON body above is a string. Here's how we can do that.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    isActive: "boolean",
    teams: {
      type: "array",
      items: "string",
    },
  },
};

For our teams property, we used that object notation check that it was an array with the type property. But we also see the items property. Setting this to a string value will verify that each item in the array is a string. Obviously you could put "number" or "boolean" or whatever other type there in that items property to test that every element in the array is that type.

But what if our JSON structure was more complicated? What if the array contained objects with multiple properties? Let's assume our JSON body is this...

{
    id: 5,
    firstName: "George",
    lastName: "Brett",
    isActive: false,
    teams: [
        {
            name: "Kansas City Royals"
            firstSeason: 1973,
            lastSeason: 1994
        }
    ]
}

We would define this schema like this:

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    isActive: "boolean",
    teams: {
      type: "array",
      items: {
        type: "object",
        properties: {
          name: "string",
          firstSeason: "number",
          lastSeason: "number",
        },
      },
    },
  },
};

So, rather than setting items to a string, this time we set it to another object. This effectively is a nested sub-schema. We could keep going with this as deep as we needed to go.

Alright great. George Brett is long retired, so he has a firstSeason and lastSeason property. However, let's say in our schema that if a player is currently active the lastSeason property will be absent from the response. We still want to make sure that if it is present it is a number. But we don't want our schema to fail if it's not there.

This is where the optional property comes in.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    isActive: "boolean",
    teams: {
      type: "array",
      items: {
        type: "object",
        properties: {
          name: "string",
          firstSeason: "number",
          lastSeason: {
            type: "number",
            optional: true,
          },
        },
      },
    },
  },
};

Well that was easy! Okay, that works when the field is absent for active players. What if lastSeason is always there, but instead it is null when they are still active? No problemo!

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    isActive: "boolean",
    teams: {
      type: "array",
      items: {
        type: "object",
        properties: {
          name: "string",
          firstSeason: "number",
          lastSeason: ["number", "null"],
        },
      },
    },
  },
};

If we set any of our properties to an array of strings, instead of a string, the schema will verify that it is one of those types.

Let's keep amping up the game. Now, rather than just the type, we also want our schema to have an opinion on the actual value. So first, let's start when there are a small set of allowed values. For example, position in baseball. So let's assume our JSON body is this (trimmed it down for simplicity):

{
    id: 5,
    firstName: "George",
    lastName: "Brett",
    positionsPlayed: [ "1b", "3b", "dh" ]
}

We could validate that all of the values in the positionsPlayed array are a valid baseball position.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    positionsPlayed: {
      type: "array",
      properties: {
        type: "string",
        enum: ["1b", "2b", "ss", "3b", "of", "sp", "rp", "c", "dh"],
      },
    },
  },
};

And that works fine, but baseball players also have numbers on their jersey. But no baseball player is going to have a number over 99, so we want to make sure that it seems valid. Here's our new JSON body:

{
    id: 5,
    firstName: "George",
    lastName: "Brett",
    jerseyNumber: 5
}

So let's use a regular expression to verify the jerseyNumber.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    jerseyNumber: {
      type: "number",
      pattern: /^[0-9]{1,2}$/,
    },
  },
};

We could have also done this with another property which is test. This property is a function that allows you to run whatever kind of logic you want. So let's change our last one to use a test callback instead.

const schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
    jerseyNumber: {
      type: "number",
      test: function (value) {
        return value >= 0 && value < 100;
      },
    },
  },
};

Obviously using the test function you could get more complicated with your logic. Besides the first value argument called above, the test method also receives a second opts argument. This contains the following properties:

  • path = The path of the current item
  • parent = The last parent item, which would be the array or object this propert is a part of
  • root = The root document that we are evaluating

This allows us to potentially look back at previous values to make sure the current one makes sense relative to the others. For example, it would not make sense for firstSeason to be after lastSeason. So let's test that.

Here's the JSON structure that we are testing:

{
    id: 5,
    firstName: "George",
    lastName: "Brett",
    firstSeason: 1973,
    lastSeason: 1994
}

We will apply this schema to be sure the season years are valid:

const schema = {
    properties: {
        id: "number",
        firstName: "string",
        lastName: "string",
        firstSeason: "number",
        lastSeason: {
            type: [ "number", "null" ]
            test: function(value, opts) {
                return (
                    value === null ||
                    value >= opts.parent.firstSeason
                );
            }
        }
    }
}

JsonValidator

This is a class that allows you to validate a schema.

The constructor accepts the schema and JSON document to be evaluated.

const personSchema: Schema = {
  properties: {
    id: "number",
    firstName: "string",
    lastName: "string",
  },
};
const jsonBody = {
  id: 234,
  firstName: "Karl",
  lastName: "Snyder",
};

const validator = new JsonValidator.validate(schema, jsonBody);
if (validator.isValid) {
  console.log("Valid!");
} else {
  console.log(validator.errors);
}

Static Method

JsonValidator.validate(schema: any, root: any): JsonValidator

Creates a new instance of JsonValidator and runs the validate method on it. This is shorthand.

Properties

errors: ValidationError[]

Gives you the list of errors encountered during the validation process. This will be empty until the validate() method is run.

isValid: boolean

Whether the document was true or false. Note: If validate() has not been run yet, this will always be true.

Methods

validate(schema: any, root: any): JsonValidator

Executes the validation process.