Just getting started!
Working on getting the tokenizer and grammer creating a proper-looking struct.
A JSON Schema generator.
JSON Schema is extremely verbose and repetative, which makes writing it by hand cumbersome, error-prone, and trying (at best) for a human. Hatcher aims to make it... well, at least bearable. It is implemented in Racket.
Hatcher's design is guided by the following principles:
- Abstraction - Why write a nested block when one (or no) word will do.
- Inheritance - DRY it out.
- Organization - Trivially import pieces of schema into each other.
- Completeness - Support all features of JSON Schema 7.0.
- Italics indicate a word that is a concept in the language, but isn't a keyword.
Code
indicates a language keyword or example.Italic code
indicates JSON Schema (except in blocks, which are labeled). See the docs linked above if needed.
The basic form is the defintion, which consists of a name and key: value fields. The name must start with a capital letter.
SomeDefinition
properties:
field: "value"
A basic defintion can have any fields legal in JSON Schema, and can be imported into any other definition of the same type. Definitions in the same file must be seperated by a blank line.
Also important to note, additionalProperties
and additionalItems
are set to false
by default, but can be enabled by specifically setting them to true
.
Qualifiers can be used to create qualified defintions. A qualified defintion may have specific required fields, or may render a certain way. They start with a lower-case letter and are written before the name in a definition: main ConfigSchema
. These qualifiers are included:
An object
that defines the top-level configuration. There must be exactly one main
defintion, and all imports must be relative to and either next to or below it in the file structure. These fields are required, all of which correspond to JSON Schema:
$id
$schema
title
properties
Categories will turn into groups of the things extending them in the schema using anyOf
. These can be any type and contain any fields. Definitions in a category will inherit all fields from the category and can override any of them. Nested overrides will be merged with the child's fields overriding anything in the parent's.
We haven't seen inheritance yet, but this should be pretty clear:
category Address
properties:
street:
type: "string"
HomeAddress /Addresses
addressType: "home"
JSON Schema:
{
"definitions": {
"Address": {
"anyOf": [
{
"$ref": "#/definitions/HomeAddress"
}
]
},
"HomeAddress": {
"additionalProperties": false,
"properties": {
"addressType": {
"const": "home"
},
"street": {
"type": "string"
}
},
"type": "object"
}
}
}
A defintion must have a type that matches with the types in JSON Schema. Types are written after the qualifier, if present, and before the defintion name. The type can be ommitted if it is object
.
array ToDoItems
items:
type: "string"
JSON Schema:
{
"definitions": {
"ToDoItems": {
"additionalItems": false,
"items": {
"type": "string"
},
"type": "array",
}
}
}
Fields in a definition are seperated by new lines, and indentation is used to defined nesting. Field values that are strings must be "double quoted"
.
Fields can have one or more of the following attributes that denote something specific about the field. These are written after the field name and bfore the colon: field*: value
.
*
: required!
: immutable- any definitions that inhert this field cannot change it.
+
: abstract- These take a JSON-Schema like type field describing the field.
- Abstract fields must be implemented on any definitions that inherit them.
- See example at the end.
Paths all begin with a /
, .
, or ..
. Path components beginning with lowercase letters indicate subfolders or files relative to the current file, and components with capitals letters indicate definitions. Dots are required for imports from other files. Paths starting with a slash indicate imports in the same file. Paths to imports in other files must specify the file and the definition, e.g. ../file/Parent
.
Inheritance is accomplishe by putting one or more paths after the name, e.g. SomeDefinition: ../file/Parent
. The only restriction on Inheritance is that a defintion may only inherit from another with the same type.
./main
main Schema
properties:
$id: "http://hatcher-example.com/schema.json"
$schema: "http://json-schema.org/draft-07/schema#"
title: "Hatcher Example Schema"
patternProperties: {
/^.+$/: ./address/Address
./address
category Address
properties:
addressType+:
enum: [
"business"
"home"
]
zipCode: 55555
./homeAddress
HomeAddress ./address/Address
properties:
AddressType: "home"
street:
type: "string"
owners: /Owners
array Owners
items:
type: "string"
JSON Schema:
{
"$id": "http://hatcher-example.com/schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"patternProperties": {
"^.+$": {
"$ref": "/definitions/Address"
}
},
"definitions": {
"Address": {
"anyOf": [
{
"$ref": "#/definitions/HomeAddress"
}
]
},
"HomeAddress": {
"additionalProperties": false,
"properties": {
"addressType": "home",
"street": {
"type": "string"
},
"owners": {
"$ref": "#/definitions/Owners"
},
"zipCode": 55555
},
"type": "object"
},
"Owners": {
"additionalItems": false,
"items": {
"type": "string"
},
"type": "array"
}
},
"title": "Hatcher Example Schema"
}