Internet Engineering Task Force C. Kappestein, Ed.
Internet-Draft September 17, 2017
Intended status: Informational
Expires: March 21, 2018

JSON Schema Code: JSON Schema rules to simplify code generation
draft-kappestein-json-schema-code-00

Abstract

JSON Schema provides many ways to describe a JSON structure. The price of this flexibility is that it is difficult for code generators to understand a JSON Schema. This specification restricts the JSON Schema keywords to a subset with a deterministic behaviour.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on March 21, 2018.

Copyright Notice

Copyright (c) 2017 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.


Table of Contents

1. Introduction

This vocabulary restricts the JSON Schema Validation keywords to a clear subset which removes ambiguities and inherent invalid schemas.

If a JSON Schema follows these rules it is easier for processors and code generators to consume. This leads to better results and maintainability of a schema.

This specification should be seen as extension to the JSON Schema draft-wright-json-schema-validation-00 specification.

2. Conventions and Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

The terms "JSON", "JSON text", "JSON value", "member", "element", "object", "array", "number", "string", "boolean", "true", "false", and "null" in this document are to be interpreted as defined in RFC 7159.

3. Overview

This document describes the JSON Schema restriction rules and all available validation keywords and their corresponding behaviour. It contains also a JSON Schema to validate whether a JSON Schema complies to these rules.

4. Restriction rules

In the JSON Schema core specification all keywords are optional and are applied in context with the actual data. The idea of this specification is to build schemas which have a distinct meaning independent of the actual data and which can be used to generate different representations i.e. model classes of a specific programming language. Because of this we must restrict existing keywords and must make specific keywords mandatory depending on the context.

In this specification every schema is exactly assigned to a specific type of schema: Definition, Combination or Reference. The distinction is made based on the used keywords.

4.1. Definition

A definition schema is a schema which describes a concrete type. It must follow the rules:

4.2. Combination

A combination schema combines multiple schemas in a specific way. It must follow the rules:

4.3. Reference

A reference schema makes a reference to another schema. It must follow the rules:

5. Not supported keywords

The following keywords are not supported. Not supported means that they have no special meaning in this specification and code generators should ignore those keywords.

6. Validation keywords

This specification restricts the usage of the validation keywords. Which keywords are allowed depends on the schema type.

The following chapter lists all available keywords and describes the concrete behaviour.

If not otherwise noted the behaviour of a keyword is identical to the JSON Schema validation specification.

6.1. Definition keywords

The following keywords can be used to describe a definition schema.

6.1.1. Common keywords

The following keywords can be used in any definition schema:

6.1.1.1. title

The value of this keyword MUST be a string. Should only contain the characters "A-Z", "a-z" and "_".

Should be a distinct word which represents this schema, may be used to generate i.e. class names or other identifiers.

Schemas with the same title should represent the same constraints since a processor could merge multiple schemas and thus remove duplicate schemas.

6.1.1.2. description

The value of this keyword MUST be a string.

Contains a general description of this property. Should only contain simple text and no line breaks since the description is may be used in code comments or other character sensitive environments.

6.1.1.3. type

The value of this keyword MUST be a string.

String values MUST be one of the six primitive types ("boolean", "object", "array", "number", or "string"), or "integer" which matches any number with a zero fractional part.

6.1.2. Object keywords

If a schema has a "type" keyword which is "object" the following validation keywords can be used:

An object must be either a struct or map type. A struct object contains a set of fixed properties and a map object contains variable key value entries.

Each object type MUST have also a "title" keyword. The title may be used by a generator to determine a class name for a schema.

6.1.2.1. Struct keywords

A struct is an object which MUST have at least a "type", "title" and "properties" keyword.

                                
{
    "title": "Person",
    "type": "object",
    "properties": {
        "forname": {
            "type": "string"
        },
        "lastname": {
            "type": "string"
        }
    }
}

                            

6.1.2.2. Map keywords

A map is an object which MUST have at least a "type", "title" and "additionalProperties" keyword.

                                
{
    "title": "Config",
    "type": "object",
    "additionalProperties": {
        "type": "string"
    }
}

                            

6.1.3. Array keywords

If a schema has a "type" keyword which is "array" the following validation keywords can be used:

6.1.4. Scalar keywords

If a schema has a "type" keyword which is either "boolean", "number" or "string" the following validation keywords can be used:

6.1.4.1. format

In the context of code generation the "format" keyword gives a hint about the concrete data type of a property. While the JSON schema validation specification defines many formats this specification supports only the formats which are usable in the context of code generation. I.e. if the format is "date-time" a code generator could utilize the standard date class of the target programming language. A code generator should consider the following formats:

6.1.4.2. Boolean keywords

If a schema has a "type" keyword which is "boolean" no validation keywords are available.

6.1.4.3. Number keywords

If a schema has a "type" keyword which is either "number" or "integer" the following validation keywords can be used:

6.1.4.4. String keywords

If a schema has a "type" keyword which is "string" the following validation keywords can be used:

6.2. Combination keywords

The following keywords can be used to describe a combination schema:

6.2.1. allOf

The all of array should be used to express inheritance. The array should list the most generic definition at the first position of the array and the most specific definition at the bottom.

Through this code generators could implement inheritance alongside the array.

Also it is possible to aggregate all schemas into a single schema. Since the all of array can only contain schemas of type "object" it is clear that the result is also always an schema of type "object".

                            
{
    "allOf": [{
        "$ref": "#/definitions/person"
    }, {
        "title": "teacher",
        "type": "object",
        "properties": {
            "classroom": {
                "type": "string"
            }
        }
    }]
}

                        

6.3. Reference keywords

The following keywords can be used to describe a reference schema:

7. Schema

The following JSON Schema can be used to validate whether a JSON Schema follows these rules.

                    
{
  "description": "A strict JsonSchema meta schema to simplify code generation",
  "oneOf": [{
    "$ref": "#/definitions/definition"
  }, {
    "$ref": "#/definitions/combination"
  }, {
    "$ref": "#/definitions/reference"
  }],
  "definitions": {
    "common": {
      "description": "Common properties which can be used in any schema",
      "title": "common",
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "description": "Distinct word which represents this property, may be used to generate i.e. class names or other identifier"
        },
        "description": {
          "type": "string",
          "description": "General description of this property"
        },
        "type": {
          "type": "string",
          "description": "JSON type of the property",
          "enum": ["object", "array", "boolean", "integer", "number", "string"]
        },
        "nullable": {
          "type": "boolean",
          "description": "Whether it is possible to use a null value on this property",
          "default": false
        },
        "deprecated": {
          "type": "boolean",
          "description": "Whether this property is deprecated",
          "default": false
        }
      }
    },
    "commonScalar": {
      "description": "Properties for scalar values",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "scalar",
        "type": "object",
        "properties": {
          "format": {
            "type": "string",
            "description": "Describes the specific format of this type i.e. date-time or int64"
          },
          "enum": {
            "type": "array",
            "description": "A list of possible enumeration values",
            "items": {
              "oneOf": [{
                "type": "string"
              },{
                "type": "number"
              }]
            },
            "minItems": 1
          }
        }
      }]
    },
    "commonContainer": {
      "description": "Properties for object values",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "container",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Distinct word which represents this property, may be used to generate i.e. class names or other identifier"
          },
          "type": {
            "type": "string",
            "enum": ["object"]
          },
          "required": { "$ref": "#/definitions/stringArray" }
        },
        "required": ["title", "type"]
      }]
    },
    "object": {
      "description": "An object represents either a struct or map",
      "oneOf": [{
        "$ref": "#/definitions/objectStruct"
      }, {
        "$ref": "#/definitions/objectMap"
      }]
    },
    "objectStruct": {
      "description": "A struct contains a fix set of defined properties",
      "allOf": [{
        "$ref": "#/definitions/commonContainer"
      }, {
        "title": "struct",
        "type": "object",
        "properties": {
          "properties": {
            "type": "object",
            "additionalProperties": { "$ref": "#" }
          }
        },
        "required": ["properties"]
      }]
    },
    "objectMap": {
      "description": "A map contains variable key value entries",
      "allOf": [{
        "$ref": "#/definitions/commonContainer"
      }, {
        "title": "map",
        "type": "object",
        "properties": {
          "additionalProperties": { "$ref": "#" },
          "maxProperties": { "$ref": "#/definitions/positiveInteger" },
          "minProperties": { "$ref": "#/definitions/positiveInteger" }
        },
        "required": ["additionalProperties"]
      }]
    },
    "array": {
      "description": "An array contains an ordered list of variable values",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "array",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["array"]
          },
          "items": {
            "$ref": "#/definitions/arrayItem"
          },
          "maxItems": { "$ref": "#/definitions/positiveInteger" },
          "minItems": { "$ref": "#/definitions/positiveInteger" },
          "uniqueItems": { 
            "type": "boolean"
          }
        },
        "required": ["type", "items"]
      }]
    },
    "boolean": {
      "description": "Represents a boolean value",
      "allOf": [{
        "$ref": "#/definitions/commonScalar"
      }, {
        "title": "boolean",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["boolean"]
          }
        },
        "required": ["type"]
      }]
    },
    "number": {
      "description": "Represents a number value which contains also integer",
      "allOf": [{
        "$ref": "#/definitions/commonScalar"
      }, {
        "title": "number",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["number", "integer"]
          },
          "multipleOf": {
            "type": "number",
            "minimum": 0,
            "exclusiveMinimum": true
          },
          "maximum": {
            "type": "number"
          },
          "exclusiveMaximum": {
            "type": "boolean",
            "default": false
          },
          "minimum": {
            "type": "number"
          },
          "exclusiveMinimum": {
            "type": "boolean",
            "default": false
          }
        },
        "required": ["type"]
      }]
    },
    "string": {
      "description": "Represents a string value",
      "allOf": [{
        "$ref": "#/definitions/commonScalar"
      }, {
        "title": "string",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["string"]
          },
          "maxLength": { "$ref": "#/definitions/positiveInteger" },
          "minLength": { "$ref": "#/definitions/positiveInteger" },
          "pattern": {
            "type": "string",
            "format": "regex"
          }
        },
        "required": ["type"]
      }]
    },
    "allOf": {
      "description": "Combination keyword to validate all containing schemas",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "allOf",
        "type": "object",
        "properties": {
          "allOf": { "$ref": "#/definitions/of" }
        },
        "required": ["allOf"]
      }]
    },
    "anyOf": {
      "description": "Combination keyword to validate any containing schemas",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "anyOf",
        "type": "object",
        "properties": {
          "anyOf": { "$ref": "#/definitions/of" }
        },
        "required": ["anyOf"]
      }]
    },
    "oneOf": {
      "description": "Combination keyword to validate exactly one containing schemas",
      "allOf": [{
        "$ref": "#/definitions/common"
      }, {
        "title": "oneOf",
        "type": "object",
        "properties": {
          "discriminator": { "$ref": "#/definitions/discriminator" },
          "oneOf": { "$ref": "#/definitions/of" }
        },
        "required": ["oneOf"]
      }]
    },
    "discriminator": {
      "description": "Adds support for polymorphism. The discriminator is an object name that is used to differentiate between other schemas which may satisfy the payload description",
      "title": "discriminator",
      "type": "object",
      "properties": {
        "propertyName": {
          "type": "string",
          "description": "The name of the property in the payload that will hold the discriminator value"
        },
        "mapping": {
          "type": "object",
          "description": "An object to hold mappings between payload values and schema names or references",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "required": ["propertyName"]
    },
    "positiveInteger": {
      "description": "Positive integer value",
      "type": "integer",
      "minimum": 0
    },
    "stringArray": {
      "description": "Array string values",
      "type": "array",
      "items": { "type": "string" },
      "minItems": 1
    },
    "of": {
      "description": "Combination values",
      "type": "array",
      "items": {
        "$ref": "#/definitions/objectOrReference"
      }
    },
    "objectOrReference": {
      "description": "Object or reference value",
      "oneOf": [{
        "$ref": "#/definitions/object"
      }, {
        "$ref": "#/definitions/reference"
      }]
    },
    "arrayItem": {
      "description": "Allowed values of an array item",
      "oneOf": [{
        "$ref": "#/definitions/object"
      }, {
        "$ref": "#/definitions/boolean"
      }, {
        "$ref": "#/definitions/number"
      }, {
        "$ref": "#/definitions/string"
      }, {
        "$ref": "#/definitions/reference"
      }]
    },
    "definition": {
      "description": "Represents a concrete type definition",
      "oneOf": [{
        "$ref": "#/definitions/object"
      }, {
        "$ref": "#/definitions/array"
      }, {
        "$ref": "#/definitions/boolean"
      }, {
        "$ref": "#/definitions/number"
      }, {
        "$ref": "#/definitions/string"
      }]
    },
    "combination": {
      "description": "Represents a combination of schemas",
      "oneOf": [{
        "$ref": "#/definitions/allOf"
      }, {
        "$ref": "#/definitions/anyOf"
      }, {
        "$ref": "#/definitions/oneOf"
      }]
    },
    "reference": {
      "description": "Represents a reference to another schema",
      "title": "reference",
      "type": "object",
      "properties": {
        "$ref": {
          "type": "string"
        }
      },
      "required": ["$ref"]
    }
  }
}

                

8. References

8.1. Normative References

[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002.
[RFC3986] Berners-Lee, T., Fielding, R. and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005.
[RFC6901] Bryan, P., Zyp, K. and M. Nottingham, "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, April 2013.
[RFC7159] Bray, T., "The JavaScript Object Notation (JSON) Data Interchange Format", RFC 7159, DOI 10.17487/RFC7159, March 2014.

8.2. Informative References

[json-schema-validation] Wright, A. and G. Luff, "JSON Schema Validation: A Vocabulary for Structural Validation of JSON", Internet-Draft draft-wright-json-schema-validation-00, October 2016.
[RFC7231] Fielding, R. and J. Reschke, "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content", RFC 7231, DOI 10.17487/RFC7231, June 2014.

Appendix A. Acknowledgments

Thanks to the JSON Schema and OpenAPI team and contributors.

Appendix B. ChangeLog

[CREF1]This section to be removed before leaving Internet-Draft status.

draft-kappestein-json-schema-code-00

Author's Address

Christoph Kappestein (editor) EMail: christoph.kappestein@gmail.com