Skip to content

lisad/strict-jsmeta

Repository files navigation

Strict meta-schema for JSON Schemas.

The standard meta-schema for JSON Schemas (2020-12 version) is useful although it is not strict. If a schema author wants to apply more strict validation to their schema, in order to close a number of common holes or fix a number of common oversights, a strict meta-schema could also be useful. Making a JSON schema correspond to the strict meta-schema will probably make it longer, so less concise and readable when documentation is the point of the schema rather than strict validation.

We're inspired by this paper. The authors surveyed tens of thousands of schema documents and found many assumed types and type mismatches, along with other presumed errors.

Some principles for 'strict':

  • Type should usually be stated, not inferred. Mistyping was one of the most common errors found.
  • When a type is stated, the other keywords operating in scope should not be inconsistent.
  • When a type is stated, meta-data (examples, default) should not be inconsistent.

Quick use guide

This repository has a number of passing and failing schemas in a test suite to validate that the strict meta-schema catches the issues it's supposed to. Setup the project and run 'pytest' to confirm.

To validate your own schemas, use code similar to the validators in validators.py, or the equivalent in any other programming language with a JSON Schema validation library built-in or added-on. Be sure to also use strict JSON parsing. By the time JSON is parsed, errors like duplicate keys in the same location (e.g. defining a value as "string" then redefining as "number") cannot be caught by a meta-schema.

You are welcome to reference the strict schemas in /schemas or copy for your own usage.

Illustrative Example

  {
    "description": "Movie data model - passes the minimum requirements for a valid schema",
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "length": { "type": "integer", "minLength": 10 },
      "rating": { "enum": ["G", "PG", "PG-13", "R", "NC-17"] },
      "reviews": {
        "items": {
          "type": "object",
          "properties": {
              "comment": { "type": "string" },
              "commentDate": { "type": "string", "format": "date" }
          },
          "required": ["comment", "commentDate"]
        }
      }
    },
    "required": ["title", "rating"]
  }

This simple schema for movies is still helpful, but a few things could go wrong.

First, the "minLength" keyword is defined for strings, not integers. Surely the author of the schema meant "minimum", but since they used "minLength", it will never be applied. Movies of length 0 or 1 will be allowed by this schema.

More subtle, the rating field includes enum values that are strings, but doesn't strictly state that the rating must be a string. This doesn't cause errors any time soon. Eventually, schema drift will be harder to detect - somebody could add a 17 value to the enum and that could break code that assumed ratings are all supposed to be strings. Form generation code using this schema might pick sub-optimal widgets, not knowing the values are all supposed to be strings.

Finally, the "items" keyword is used to make sure that reviews of the movie have both a comment and a commentDate. But since the "reviews" property is never limited to being an "array", somebody could submit a movie with a "reviews" value of simply "no". This presents a validation hole where by using a different type, submitted data could pass the validator and bypass the intent.

Catching and fixing these errors makes the schema more foolproof for validation and for code gen. The following schema passes strict AND base validation, and more completely and clearly constrains valid data.

  {
    "description": "Movie data model - passes strict validation as well.",
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "length": { "type": "integer", "minimum": 10 },
      "rating": { 
        "type": "string",
        "enum": ["G", "PG", "PG-13", "R", "NC-17"]
      },
      "reviews": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
              "comment": { "type": "string" },
              "commentDate": { "type": "string", "format": "date" }
          },
          "required": ["comment", "commentDate"]
        }
      }
    },
    "required": ["title", "rating"]
  }

Further work

Require more things to be stated explicitly

Some types of things could be made more explicit but at the cost of getting more and more verbose.

For example, while the author probably meant to put "additionalProperties" at the same level as "properties", this instead defines a property called "additionalProperties". This is perfectly legal.

  {
    "type": "object",
    "properties": {
      "image": { "type": "string" },
      "additionalProperties": false
    }
  }

One approach would be to require "additionalProperties" to be explicit whenever "properties" is used, because then the author would notice it was at the wrong layer. But that would be verbose all the time and maybe not catch very many errors.

Forbid all unrecognized keywords

In this example, "min" and "max" are supposed to be "minimum" and "maximum", but to forbid mis-spellings with a meta-schema would involve forbidding all unrecognized keywords at a level where a keyword is expected.

  {
    "type": "number",
    "min": 100,
    "max": 599,
    "integer": true
  }

That would catch this kind of error too, when the author accidentally creates whole schema sections that doesn't do anything.

  {
    "type": "object",
    "date_of_birth": {
      "type": "string",
      "format": "date",
    }
  }

Since "date_of_birth" isn't a keyword, and it isn't inside the "properties" keyword, it doesn't do anything. Forbidding all unrecognized keywords is a very severe approach to preventing this kind of error.

Forbid unknown "format" values

Errors like using "format": "url" or "format": "e-mail" or "type": "int" are easy to make. (They should be "uri", "email" and "integer"). Yet forbidding all these values seems severe. The base meta-schema already catches "type": "int" so that's good.

About

A strict meta-schema for JSON Schemas themselves to validate against.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages