JSON Schema, shortly

« Return to Our Notebook

JSON Schema, shortly

a stenograph - picture by Chatsam / CC-BY-SA-3.0
optimizing that whole typety-type business thing - picture by Chatsam / CC-BY-SA-3.0

JSON Schema is a neat way to describe or prescribe structural expectations of JSON documents (or, indeed, any data structure, let it be a JavaScript plain object or the equivalent in another language). But JSON schemas are themselves JSON documents and, while machines love a good ol’ JSON format, let’s face it: for us humans it’s a lengthy, picky, and mildly onerous format to write and read.

Fortunately, there are many ways to craft JSON schemas while circumventing most of its JSON-born tediousness. Let me show you a few of them.

We’ll need a volunteer from the audience…

Talking abstract is boring. Let’s work on a real yet comfortably-sized example:

{
   "$id" : "http://aotds/battle/ship",
   "title" : "Ship",
   "definitions" : {
      "course" : {
         "type" : "array",
         "description" : "projected movement for new turn",
         "items" : {
            "type" : "object",
            "properties" : {
               "heading" : {
                  "$ref" : "#/definitions/heading"
               },
               "coords" : {
                  "$ref" : "#/definitions/coords"
               }
            }
         }
      },
      "heading" : {
         "type" : "number",
         "description" : "forward angle of the object",
         "minimum" : 0,
         "maximum" : 12
      },
      "coords" : {
         "type" : "array",
         "items" : {
             "type": "number"
         },
         "minItems" : 2,
         "maxItems" : 2
      },
      "velocity" : {
         "description" : "speed of the object",
         "type" : "number",
         "minimum" : 0
      }
   },
   "type" : "object",
   "properties" : {
      "navigation" : {
         "type" : "object",
         "properties" : {
            "heading" : {
               "$ref" : "#/definitions/heading"
            },
            "course" : {
               "$ref" : "#/definitions/course"
            },
            "coords" : {
               "$ref" : "#/definitions/coords"
            },
            "velocity" : {
               "$ref" : "#/definitions/velocity"
            }
         }
      }
   }
}

As we can see, it’s in no way the most horrendous format we’ll ever deal with. Still, it’s not something I’d expect anyone to peruse – or worse, type out – with glee.

Sidestep the problem to the left…

JSON is a pain to write mostly because of its exuberant quoting. And then there is this thrice darned phobia of trailing commas. But while JSON is the canonical format for JSON schemas, nobody said that you had to write or read it in that format. As long it’s possible to convert to and fro, there is no good reason not to use a different format for soft, tender human consumption.

Just a tad

To stay JSON formal, but loosen its tie a little bit, there is JSON5.

hide before/after comparison
Before
{
   "$id" : "http://aotds/battle/ship",
   "title" : "Ship",
   "definitions" : {
      "course" : {
         "type" : "array",
         "description" : "projected movement for new turn",
         "items" : {
            "type" : "object",
            "properties" : {
               "heading" : {
                  "$ref" : "#/definitions/heading"
               },
               "coords" : {
                  "$ref" : "#/definitions/coords"
               }
            }
         }
      },
      "heading" : {
         "type" : "number",
         "description" : "forward angle of the object",
         "minimum" : 0,
         "maximum" : 12
      },
      "coords" : {
         "type" : "array",
         "items" : {
             "type": "number"
         },
         "minItems" : 2,
         "maxItems" : 2
      },
      "velocity" : {
         "description" : "speed of the object",
         "type" : "number",
         "minimum" : 0
      }
   },
   "type" : "object",
   "properties" : {
      "navigation" : {
         "type" : "object",
         "properties" : {
            "heading" : {
               "$ref" : "#/definitions/heading"
            },
            "course" : {
               "$ref" : "#/definitions/course"
            },
            "coords" : {
               "$ref" : "#/definitions/coords"
            },
            "velocity" : {
               "$ref" : "#/definitions/velocity"
            }
         }
      }
   }
}
After
{
   $id : "http://aotds/battle/ship",
   title : "Ship",
   definitions : {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            type : "object",
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            }
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
   },
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         }
      }
   }
}
{
   $id : "http://aotds/battle/ship",
   title : "Ship",
   definitions : {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            type : "object",
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            }
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
   },
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         }
      }
   }
}

Granted, not a huge difference. Still, the removal of all those pesky quotes does clear up the air.

A lot

Now, if we really want to cut on the noise, we could go full-tilt and work with YAML.

hide before/after comparison
Before
{
   "$id" : "http://aotds/battle/ship",
   "title" : "Ship",
   "definitions" : {
      "course" : {
         "type" : "array",
         "description" : "projected movement for new turn",
         "items" : {
            "type" : "object",
            "properties" : {
               "heading" : {
                  "$ref" : "#/definitions/heading"
               },
               "coords" : {
                  "$ref" : "#/definitions/coords"
               }
            }
         }
      },
      "heading" : {
         "type" : "number",
         "description" : "forward angle of the object",
         "minimum" : 0,
         "maximum" : 12
      },
      "coords" : {
         "type" : "array",
         "items" : {
             "type": "number"
         },
         "minItems" : 2,
         "maxItems" : 2
      },
      "velocity" : {
         "description" : "speed of the object",
         "type" : "number",
         "minimum" : 0
      }
   },
   "type" : "object",
   "properties" : {
      "navigation" : {
         "type" : "object",
         "properties" : {
            "heading" : {
               "$ref" : "#/definitions/heading"
            },
            "course" : {
               "$ref" : "#/definitions/course"
            },
            "coords" : {
               "$ref" : "#/definitions/coords"
            },
            "velocity" : {
               "$ref" : "#/definitions/velocity"
            }
         }
      }
   }
}
After
---
$id: http://aotds/battle/ship
title: Ship
definitions:
  course:
    type: array
    description: projected movement for new turn
    items:
      type: object
      properties:
        heading:
          $ref: '#/definitions/heading'
        coords:
          $ref: '#/definitions/coords'





  heading:
    type: number
    description: forward angle of the object
    minimum: 0
    maximum: 12

  coords:
    type: array
    items:
      type: number
    minItems: 2
    maxItems: 2


  velocity:
    description: speed of the object
    type: number
    minimum: 0


type: object
properties:
  navigation:
    type: object
    properties:
      heading:
        $ref: '#/definitions/heading'

      course:
        $ref: '#/definitions/course'

      coords:
        $ref: '#/definitions/coords'

      velocity:
        $ref: '#/definitions/velocity'
---
$id: http://aotds/battle/ship
title: Ship
definitions:
  course:
    type: array
    description: projected movement for new turn
    items:
      type: object
      properties:
        heading:
          $ref: '#/definitions/heading'
        coords:
          $ref: '#/definitions/coords'
  heading:
    type: number
    description: forward angle of the object
    minimum: 0
    maximum: 12
  coords:
    type: array
    items:
      type: number
    minItems: 2
    maxItems: 2
  velocity:
    description: speed of the object
    type: number
    minimum: 0
type: object
properties:
  navigation:
    type: object
    properties:
      heading:
        $ref: '#/definitions/heading'
      course:
        $ref: '#/definitions/course'
      coords:
        $ref: '#/definitions/coords'
      velocity:
        $ref: '#/definitions/velocity'

Love or hate whitespace-based formats, be honest for a minute here: ain’t that much better to read?

Sidestep the problem to the right…

Using a different static format already helps, but maybe we could escalate things and go dynamic. Like, how about leveraging the powers of JavaScript to help us with the repetitive and boring stuff?

hide before/after comparison
Before
{
   "$id" : "http://aotds/battle/ship",
   "title" : "Ship",
   "definitions" : {
      "course" : {
         "type" : "array",
         "description" : "projected movement for new turn",
         "items" : {
            "type" : "object",
            "properties" : {
               "heading" : {
                  "$ref" : "#/definitions/heading"
               },
               "coords" : {
                  "$ref" : "#/definitions/coords"
               }
            }
         }
      },
      "heading" : {
         "type" : "number",
         "description" : "forward angle of the object",
         "minimum" : 0,
         "maximum" : 12
      },
      "coords" : {
         "type" : "array",
         "items" : {
             "type": "number"
         },
         "minItems" : 2,
         "maxItems" : 2
      },
      "velocity" : {
         "description" : "speed of the object",
         "type" : "number",
         "minimum" : 0
      }
   },
   "type" : "object",
   "properties" : {
      "navigation" : {
         "type" : "object",
         "properties" : {
            "heading" : {
               "$ref" : "#/definitions/heading"
            },
            "course" : {
               "$ref" : "#/definitions/course"
            },
            "coords" : {
               "$ref" : "#/definitions/coords"
            },
            "velocity" : {
               "$ref" : "#/definitions/velocity"
            }
         }
      }
   }
}
After
export default {
   $id : "http://aotds/battle/ship",
   title : "Ship",
   definitions : {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
   },
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
};
export default {
   $id : "http://aotds/battle/ship",
   title : "Ship",
   definitions : {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
   },
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
};

I know what you are thinking, that doesn’t give us nothing more json5 already did, but hang on, just hang on…

Divide and conquer

One thing that JavaScript makes possible is splintering the whole schema into smaller, easier to digest segments. For a start, let’s segregate the definitions from the main object schema. That won’t make the code any shorter, but we’ll now have two pieces that we can work on independently. If things grow out of control, we could even go ahead and move them into different files.

hide before/after comparison
Before
export default {
   $id : "http://aotds/battle/ship",
   title : "Ship",
   definitions : {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
   },
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
};
After
const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
}
const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
}

Pseudo-types

Next in line: all those $ref are rather verbose. Why not define succinct, easier to read aliases, and use them as pseudo-types?

hide before/after comparison
Before







const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            properties : {
               heading : {
                  $ref : "#/definitions/heading"
               },
               coords : {
                  $ref : "#/definitions/coords"
               }
            },
            type : "object"
         }
      },
      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading : {
               $ref : "#/definitions/heading"
            },
            course : {
               $ref : "#/definitions/course"
            },
            coords : {
               $ref : "#/definitions/coords"
            },
            velocity : {
               $ref : "#/definitions/velocity"
            }
         },
      }
   }
}
After
const defLink = name => ({ $ref: '#/definitions/' + name });

const heading  = defLink('heading');
const coords   = defLink('coords');
const course   = defLink('course');
const velocity = defLink('velocity');

const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            type : "object",
            properties : {
               heading,
               coords
            }
         }
      },




      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}
const defLink = name => ({ $ref: '#/definitions/' + name });

const heading  = defLink('heading');
const coords   = defLink('coords');
const course   = defLink('course');
const velocity = defLink('velocity');

const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            type : "object",
            properties : {
               heading,
               coords
            }
         }
      },




      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}

Pseudo-types, swankier

We took a first step into cutting the holistic schema into logical parts, but the definitions are still set in a block. If we think about it, there is no good reason for them to be blobbed together thus. Also, wouldn’t it be nice to also herd the logic such that defining the types and their aliases would be a one-step operation rather than a two-step dance?

Cue in my own humble npm module json-schema-shorthand which offers a few helper functions that help smooth the abrasive bits of the schema.

First of the bunch: add_definition, which appends a definition to a main collection while returning its alias.

hide before/after comparison
Before
const defLink = name => ({ $ref: '#/definitions/' + name });

const heading  = defLink('heading');
const coords   = defLink('coords');
const course   = defLink('course');
const velocity = defLink('velocity');

const definitions = {
      course : {
         type : "array",
         description : "projected movement for new turn",
         items : {
            type : "object",
            properties : {
               heading,
               coords
            }
         }
      },




      heading : {
         type : "number",
         description : "forward angle of the object",
         minimum : 0,
         maximum : 12
      },
      coords : {
         type : "array",
         items : {
             type: "number"
         },
         minItems : 2,
         maxItems : 2
      },
      velocity : {
         description : "speed of the object",
         type : "number",
         minimum : 0
      }
};

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}
After
import { add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);



const course = add_def('course', {
    type : "array",
    description : "projected movement for new turn",
    items : {
        type : "object",
        properties : { heading, coords }
    }
});







const heading = add_def( 'heading', {
    type : "number",
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
});
const coords = add_def('coords', {
    type : "array",
    items : {
        type: "number"
    },
    minItems : 2,
    maxItems : 2
});
const velocity = add_def('velocity', {
    description : "speed of the object",
    type : "number",
    minimum : 0
});


export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}
import { add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);



const course = add_def('course', {
    type : "array",
    description : "projected movement for new turn",
    items : {
        type : "object",
        properties : { heading, coords }
    }
});







const heading = add_def( 'heading', {
    type : "number",
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
});
const coords = add_def('coords', {
    type : "array",
    items : {
        type: "number"
    },
    minItems : 2,
    maxItems : 2
});
const velocity = add_def('velocity', {
    description : "speed of the object",
    type : "number",
    minimum : 0
});


export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}

Tired of typing typical type terms?

The { type: ... } declarations are also something that appears over and over again, and they are kind of long-winded. Let’s shrink that too with helper functions.

hide before/after comparison
Before
import { add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def('course', {
    type : "array",
    description : "projected movement for new turn",
    items : {
        type : "object",
        properties : { heading, coords }
    }
});

const heading = add_def( 'heading', {
    type : "number",
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
});

const coords = add_def('coords', {
    type : "array",
    items : {
        type: "number"
    },
    minItems : 2,
    maxItems : 2
});

const velocity = add_def('velocity', {
    description : "speed of the object",
    type : "number",
    minimum : 0
});

export default {
   definitions,
   $id : "http://aotds/battle/ship",
   title : "Ship",
   type : "object",
   properties : {
      navigation : {
         type : "object",
         properties : {
            heading,
            coords,
            course,
            velocity
         },
      }
   }
}
After
import { object, array, number, add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course', array(
    object({ heading, coords }),
    { description : "projected movement for new turn" }
));





const heading  = add_def( 'heading', number({
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
}));


const coords = add_def( 'coords', array( 'number', {
    minItems : 2,
    maxItems : 2
}));





const velocity = add_def('velocity', number({
    description : "speed of the object",
    minimum : 0
}));


export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);
import { object, array, number, add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course', array(
    object({ heading, coords }),
    { description : "projected movement for new turn" }
));





const heading  = add_def( 'heading', number({
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
}));


const coords = add_def( 'coords', array( 'number', {
    minItems : 2,
    maxItems : 2
}));





const velocity = add_def('velocity', number({
    description : "speed of the object",
    minimum : 0
}));


export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);

A few last tweaks

As a last touch, we can also use a few of the shorthand forms and extra-spec keywords that json-schema-shorthand provides — like range that combines min and max values, the detection and expansion of description strings… Y’know. Sprinkly magic.

hide before/after comparison
Before
import { object, array, number, add_definition } from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course', array(
    object({ heading, coords }),
    { description : "projected movement for new turn" }
));





const heading  = add_def( 'heading', number({
    description : "forward angle of the object",
    minimum : 0,
    maximum : 12
}));


const coords = add_def( 'coords', array( 'number', {
    minItems : 2,
    maxItems : 2
}));





const velocity = add_def('velocity', number({
    description : "speed of the object",
    minimum : 0
}));


export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);
After
import { object, array, number, add_definition }
    from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course',
    "projected movement for new turn"
    array( object({ heading, coords }) ),
);

const heading  = add_def( 'heading',
    "forward angle of the object",
    number({ range: [ 0, 12 ] }),
);


const coords = add_def( 'coords',
    array( 'number', { nbrItems: 2, })
);


const velocity = add_def( 'velocity',
    "speed of the object",
    number({ minimum : 0 }),
);

export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);
import { object, array, number, add_definition }
    from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course',
    "projected movement for new turn"
    array( object({ heading, coords }) ),
);

const heading  = add_def( 'heading',
    "forward angle of the object",
    number({ range: [ 0, 12 ] }),
);


const coords = add_def( 'coords',
    array( 'number', { nbrItems: 2, })
);


const velocity = add_def( 'velocity',
    "speed of the object",
    number({ minimum : 0 }),
);

export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);

Final form most beautiful

Now we can take a step back and compare the original JSON document with its final JavaScript incarnation.

hide before/after comparison
Before
{
   "$id" : "http://aotds/battle/ship",
   "title" : "Ship",
   "definitions" : {
      "course" : {
         "type" : "array",
         "description" : "projected movement for new turn",
         "items" : {
            "type" : "object",
            "properties" : {
               "heading" : {
                  "$ref" : "#/definitions/heading"
               },
               "coords" : {
                  "$ref" : "#/definitions/coords"
               }
            }
         }
      },
      "heading" : {
         "type" : "number",
         "description" : "forward angle of the object",
         "minimum" : 0,
         "maximum" : 12
      },
      "coords" : {
         "type" : "array",
         "items" : {
             "type": "number"
         },
         "minItems" : 2,
         "maxItems" : 2
      },
      "velocity" : {
         "description" : "speed of the object",
         "type" : "number",
         "minimum" : 0
      }
   },
   "type" : "object",
   "properties" : {
      "navigation" : {
         "type" : "object",
         "properties" : {
            "heading" : {
               "$ref" : "#/definitions/heading"
            },
            "course" : {
               "$ref" : "#/definitions/course"
            },
            "coords" : {
               "$ref" : "#/definitions/coords"
            },
            "velocity" : {
               "$ref" : "#/definitions/velocity"
            }
         }
      }
   }
}
After
import { object, array, number, add_definition }
    from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course',
    "projected movement for new turn"
    array( object({ heading, coords }) ),
);

const heading  = add_def( 'heading',
    "forward angle of the object",
    number({ range: [ 0, 12 ] }),
);


const coords = add_def( 'coords',
    array( 'number', { nbrItems: 2, })
);


const velocity = add_def( 'velocity',
    "speed of the object",
    number({ minimum : 0 }),
);

export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);
import { object, array, number, add_definition }
    from 'json-schema-shorthand';

let definitions = {};

const add_def = add_definition.bind(definitions);

const course = add_def( 'course',
    "projected movement for new turn"
    array( object({ heading, coords }) ),
);

const heading  = add_def( 'heading',
    "forward angle of the object",
    number({ range: [ 0, 12 ] }),
);


const coords = add_def( 'coords',
    array( 'number', { nbrItems: 2, })
);


const velocity = add_def( 'velocity',
    "speed of the object",
    number({ minimum : 0 }),
);

export default object(
   { navigation : object({ heading, coords, course, velocity }) },
   {
        definitions,
        $id : "http://aotds/battle/ship",
        title : "Ship"
   }
);

The JavaScript version is smaller, which is nice. It also broke the monolithic document into logical fragments, which is better. But, most importantly to my eyes, it shifted the emphasis of the written code from the “how” to the “what”, ensuring that desirable actions like adding descriptions to definitions are easy and up-front-and-center.

Enjoy!

We solve problems with technology. What can we solve for you?

Reach Out

t: 800.646.0188