json_schema

A library for parsing, inspecting and manipulating JSON Schema documents.


Keywords
elixir, elixir-lang, elixir-library, json, json-schema, parser
License
MIT

Documentation

JSON Schema

Module Version Hex Docs License

A JSON schema parser for inspection and manipulation of JSON Schema Abstract Syntax Trees (ASTs). This library is meant as a basis for writing other libraries or tools that need to use JSON schema documents. For example, a JSON schema validator that validates a JSON object according to a JSON schema specification, or a code generator that generates a data model and accompanying JSON serializers based on the JSON schema specification of an API -- the project JSON Schema to Elm is an example of such a tool.

Installation

Add :json_schema as a dependency in mix.exs:

defp deps do
  [
    {:json_schema, "~> 0.5"}
  ]
end

Usage

The words type and subschema are used interchangeable in the rest of the document.

The main API entry point is the JsonSchema module, which supports parsing a list of JSON schema files into JSON Schema ASTs via parse_schema_files, or resolving a JSON schema type given an identifier via resolve_type.

Parsing a JSON schema file into an Abstract Syntax Tree

Presuming we have the following two JSON schema files:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://example.com/circle.json",
    "title": "Circle",
    "description": "Schema for a circle shape",
    "type": "object",
    "properties": {
        "center": {
            "$ref": "http://example.com/definitions.json#point"
        },
        "radius": {
            "type": "number"
        },
        "color": {
            "$ref": "http://example.com/definitions.json#color"
        }
    },
    "required": ["center", "radius"]
}

and

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Definitions",
    "$id": "http://example.com/definitions.json",
    "description": "Schema for common types",
    "definitions": {
        "color": {
            "$id": "#color",
            "type": "string",
            "enum": [ "red", "yellow", "green", "blue" ]
        },
        "point": {
            "$id": "#point",
            "type": "object",
            "properties": {
                "x": {
                    "type": "number"
                },
                "y": {
                    "type": "number"
                }
            },
            "required": [ "x", "y" ]
        }
    }
}

we can parses the into a JSON schema AST by passing them to JsonSchema.parse_schema_files. This produces a SchemaResult containing a schema dictionary, schema_dict, and any errors or warnings generated along the way.

schema_paths = ["./path/to/json_schemas/circle.json",
                "./path/to/json_schemas/definitions.json"
               ]
schema_result = JsonSchema.parse_schema_files(schema_paths)
%JsonSchema.Parser.SchemaResult{
  errors: [],
  warnings: [],
  schema_dict: %{
    "http://example.com/circle.json" => %JsonSchema.Types.SchemaDefinition{
      description: "Schema for a circle shape",
      file_path: "/path/to/json-schemas/circle.json",
      id: URI.parse("http://example.com/circle.json"),
      title: "Circle",
      types: %{
        "#" => %JsonSchema.Types.ObjectType{
          additional_properties: nil,
          description: "Schema for a circle shape",
          name: "Circle",
          path: URI.parse("#"),
          pattern_properties: %{},
          properties: %{
            "center" => URI.parse("#/properties/center"),
            "color" => URI.parse("#/properties/color"),
            "radius" => URI.parse("#/properties/radius")
          },
          required: ["center", "radius"]
        },
        "#/properties/center" => %JsonSchema.Types.TypeReference{
          name: "center",
          path: URI.parse("http://example.com/definitions.json#point")
        },
        "#/properties/color" => %JsonSchema.Types.TypeReference{
          name: "color",
          path: URI.parse("http://example.com/definitions.json#color")
        },
        "#/properties/radius" => %JsonSchema.Types.PrimitiveType{
          description: nil,
          name: "radius",
          path: URI.parse("#/properties/radius),
          type: :number
        },
        "http://example.com/circle.json#" => %JsonSchema.Types.ObjectType{
          additional_properties: nil,
          description: "Schema for a circle shape",
          name: "Circle",
          path: URI.parse("#"),
          pattern_properties: %{},
          properties: %{
            "center" => URI.parse("#/properties/center"),
            "color" => URI.parse("#/properties/color"),
            "radius" => URI.parse("#/properties/radius")
          },
          required: ["center", "radius"]
        }
      }
    },
    "http://example.com/definitions.json" => %JsonSchema.Types.SchemaDefinition{
      description: "Schema for common types",
      file_path: "/path/to/json-schemas/definitions.json",
      id: URI.parse("http://example.com/definitions.json"),
      title: "Definitions",
      types: %{
        "#/definitions/color" => %JsonSchema.Types.EnumType{
          description: nil,
          name: "color",
          path: URI.parse("#/definitions/color"),
          type: :string,
          values: ["red", "yellow", "green", "blue"]
        },
        "#/definitions/point" => %JsonSchema.Types.ObjectType{
          additional_properties: nil,
          description: nil,
          name: "point",
          path: URI.parse("#/definitions/point"),
          pattern_properties: %{},
          properties: %{
            "x" => URI.parse("#/definitions/point/properties/x"),
            "y" => URI.parse("#/definitions/point/properties/y")
          },
          required: ["x", "y"]
        },
        "#/definitions/point/properties/x" => %JsonSchema.Types.PrimitiveType{
          description: nil,
          name: "x",
          path: URI.parse("/definitions/point/properties/x"),
          type: :number
        },
        "#/definitions/point/properties/y" => %JsonSchema.Types.PrimitiveType{
          description: nil,
          name: "y",
          path: URI.parse("#/definitions/point/properties/y"),
          type: :number
        },
        "http://example.com/definitions.json#color" => %JsonSchema.Types.EnumType{
          description: nil,
          name: "color",
          path: URI.parse("#/definitions/color"),
          type: :string,
          values: ["red", "yellow", "green", "blue"]
        },
        "http://example.com/definitions.json#point" => %JsonSchema.Types.ObjectType{
          additional_properties: nil,
          description: nil,
          name: "point",
          path: URI.parse("#/definitions/point"),
          pattern_properties: %{},
          properties: %{
            "x" => URI.parse("#/definitions/point/properties/x"),
            "y" => URI.parse("#/definitions/point/properties/y")
          },
          required: ["x", "y"]
        }
      }
    }
  }
}

The schema dictionary uses the schema ID (URI) as key and the parsed schema as value. Each parsed schema likewise contains a type dictionary, types, which uses the subschema path (URI) as key and the parsed subschema as value.

If there are no errors in the schema result, the parsed schema dictionary can then be used in your own JSON schema tool as appropriate.

Resolving a JSON Schema type from an identifier

Using the example schema dictionary from the previous section, we can use the JsonSchema.resolve_type function to lookup a subschema associated contained the schema dictionary. The resolve_type function expects:

  • the identifier of the subschema to lookup which can be either a fully qualified identifier like http://example.com/definitions.json#color, a relative identifier like #color, or a relative path like #/definitions/color when resolving a subschema inside the same parent schema.
  • the parent identifier of the subschema doing the lookup, needed for relative lookups and better error messaging,
  • the enclosing schema definition, schema_def, of the subschema doing the lookup, also needed for relative lookups and error messaging,
  • the schema dictionary of the whole set of schemas.

In the example below, we resolve the reference to color:

"color": {
  "$ref": "http://example.com/definitions.json#color"
}

from the inside the circle subschema properties.

schema_dict = schema_result.schema_dict
schema_def = schema_dict["http://example.com/circle.json"]
parent = URI.parse("#/properties")
identifier = URI.parse("#/properties/color")
lookup_result = JsonSchema.resolve_type(identifier, parent, schema_def, schema_dict)
{:ok, {color_type, parent_schema_def}} = lookup_result
parent_schema_def == schema_dict["http://example.com/circle.json"]
color_type
%JsonSchema.Types.EnumType{
    description: nil,
    name: "color",
    path: URI.parse("#/definitions/color"),
    type: :string,
    values: ["red", "yellow", "green", "blue"]
  }