Aurelia

json-schema validation

Introduction

Matt Broadstone

Matt Broadstone


validation json-schema json schema

json-schema validation

Posted by Matt Broadstone on .
Featured

validation json-schema json schema

json-schema validation

Posted by Matt Broadstone on .

Like many of you, I have been keeping a close eye on the development of Aurelia's validation plugin(s) for some time now. This truly was the last missing piece of the puzzle for me in terms of being able to fully drop external JavaScript dependencies, and a notable gap in Aurelia's stable offering during the 1.0 release.

So it was a pleasant surprise to receive a gitter notification last weekend about a new release of the plugin. Recently I have been using a lot of json-schema for data validation before dumping it into a NoSQL database (<3 RethinkDB), so I decided to see how hard it would be to implement an aurelia-validation plugin using ajv.

Turns out it was delightfully easy! Below is the code in its nascent form. It took about 10 minutes to implement and it "Just Worked™".

import {ValidationError} from 'aurelia-validation';

export class AjvValidator {  
  cache = new Map;
  ajv = new Ajv({ v5: true, allErrors: true, format: 'full' });

  validateObject(object) {
    this.parseSchema(object);
    let schemaId = this._schemaId(object);
    if (!this.cache.has(schemaId)) {
      console.warn('no schema defined for object');
      return [];
    }

    let validate = this.cache.get(schemaId).validate;
    let valid = validate(data);
    return valid ? [] : validate.errors
      .map(({ dataPath, keyword, message, params, schemaPath }) => 
        new ValidationError(dataPath, message, object, propertyName));
  }

  validateProperty(object, propertyName) {
    this.parseSchema(object);
    let schemaId = this._schemaId(object);
    if (!this.cache.has(schemaId)) {
      console.warn('no schema defined for object');
      return [];
    }

    if (!this.cache.get(schemaId).hasOwnProperty(propertyName)) {
      console.warn('property not defined in schema: ', propertyName);
      return [];
    }

    let definition = this.cache.get(schemaId);
    let validate = definition[propertyName];
    let valid = validate({ [propertyName]: object[propertyName] });
    return valid ? [] : validate.errors
      .map(({ dataPath, keyword, message, params, schemaPath }) => 
        new ValidationError(dataPath, message, object, propertyName));
  }

  _schemaId(object) {
    return (!object.schema.hasOwnProperty('id')) ?
      object.constructor.name : object.schema.id;
  }

  parseSchema(object) {
    let schema = object.schema;
    if (schema === undefined || schema === null) {
      throw new Error('object lacks schema');
    }

    let schemaId = this._schemaId(object);
    if (this.cache.has(schemaId)) {
      // schema has already been parsed
      return;
    }

    if (!schema.hasOwnProperty('properties')) {
      throw new Error('only object schemas are current supported'); 
    }

    let definition = {};
    definition.validate = this.ajv.compile(schema);

    // split schema into individual properties
    let required = schema.required || [];
    for (let property in schema.properties) {
      let subSchema = { type: 'object', properties: {} };
      subSchema.properties[property] = schema.properties[property];
      if (required.indexOf(property) !== -1) subSchema.required = [ property ];
      let validator = this.ajv.compile(subSchema);
      definition[property] = validator;
    }

    this.cache.set(schemaId, definition);
  }
}

Here's a live demo to play around with.

( Many thanks to core member @jdanyow for putting together the initial gist.run I based this off of. Have you taken a second today to thank him? You should! )

That the implementation was so straightforward immediately indicates to me that aurelia-validation has finally found its legs. I had been a (somewhat passive) participant in a number of design discussions during the development of aurelia-validation, and was very pleased to see all of my concerns had been addressed in this release.

Specifically, in previous iterations it would have been difficult to implement this plugin because it doesn't necessarily "play by the rules":

  • validators are all self-contained in ajv, so completely opting out of the validators provided by Aurelia is a requirement

  • "rules" here are just JSON objects, rather than annotations on properties, so this required a separation of concerns not previously possible with earlier versions of aurelia-validation (perhaps the biggest gripe/blocker/unicorn)

  • ajv has no concept of property-level validation, nor any plans to support such a feature, so the interface had to be flexible enough to allow me to implement that myself

Admittedly some of this was possible before but it was not obvious, and the implementations of aurelia-validation and aurelia-validatejs were closely coupled in a way that was difficult to reason about. No such trouble with this release! The new design is very intuitive and really comes down to implementing two functions. Rad.

A few notes about the current implementation:

  • Rules are currently defined as a property called schema on the object being validated. This will likely be moved over to using the new Rules metadata introduced in this commit

  • aurelia-validations default validator implementation uses a concept of ValidationRules to compile validators for your data. You'll note that for my initial implementation I chose to lazily compile validators at the moment of validation. There is definitely room for improvement here, and might actually be a use case for abstracting ValidationRules and providing them as a top-level export of the aurelia-validation plugin.

  • I mentioned before that ajv does not support property-level validation out of the box, rather the suggested approach is to use subschemas for individual properties. I've provided a naive implementation of programmatically splitting a JSON schema into subschemas for this demo, however it is far from complete and will likely fail in more complicated cases. There is a lot of work to be done here, and I would love feedback from the community to develop the feature.

I hope this gives you a taste of how powerful the new aurelia-validation is. I'm eager to hear back from you all about how this plugin might be of use, and ideally how we might collaboratively improve it - so please leave your comments below or reach out on the project's gitter channels.

All in all I'm quite pleased with the state of the art today, the new version of aurelia-validation is a powerful tool for an already great framework.

Matt Broadstone is a Lead Software Architect for hiveIO, a NYC-based cloud computing company specializing in software-defined virtual infrastructure. Interested in aurelia, virtualization or node.js? We're hiring!

Matt Broadstone

Matt Broadstone

https://aurelia.io

View Comments...