Using JSON Schema to configure highly nested modular web applications

This article describes how to use a JSON Schema file to configure an application made of RequireJS modules that may be composed by other submodules. The configuration file will be used to configure all the modules and its children, if any, from a single entry point. Furthermore the configuration file structure will allow the editing of the properties that are shared among the modules, as instance the language, in a centralized way.

Architecture

The example describes a hypothetical e-commerce web site.


root/ 
├── js 
    ├── application.js 
    ├── main.js 
    ├── submodules 
        ├── catalog 
        ├── items_table 
        ├── search 
        ├── results_table 
        ├── search_bar 
        ├── cart 
        ├── cart_summary 
        ├── payment_summary 
        ├── user_profile 

Each entry of the submodules folder represents a RequireJS module, that is composed by other modules. Each module has its own default settings:

function MODULE() {
   this.CONFIG = { 
      lang: 'en', 
      url_items: 'http://www.example.com/rest/items/'
   }; 
}

JSON Schema

A JSON Schema is a standard document used to describe a JSON file. The structure is recursive, through the use of the properties field, and it can be used to describe the nesting of the modules. The basic structure is the following:

{ 
   "$schema": "http://json-schema.org/draft-04/schema#", 
   "type": "object", 
   "title": "Online Shop", 
   "description": "The coolest e-commerce web site around.", 
   "definitions": {
      ... 
   } 
   "properties": {
      ... 
   } 
}

The properties section will contain the description of our sections, each object follows the standard structure:

{ 
   "type": "", 
   "title": "", 
   "description": "", 
   "default": "" 
}

A very useful property of the JSON Schema is the capability to reference an object defined in the definitions section. There are properties that are common among several modules, as instance the language. It is possible to define this property just once, and then reference it, as follows:

{
   "$schema": "http://json-schema.org/draft-04/schema#", 
   "type": "object", 
   "title": "Online Shop", 
   "description": "The coolest e-commerce web site around.", 
   "definitions": { 
      "lang": { 
         "title": "Language", 
         "description": "Language of the user interface.", 
         "enum": [ 
            "en", "fr", "es" 
         ] 
      } 
   },
   "properties": { 
      "a_field": { 
         "$ref": "#/definitions/lang" 
      }, 
      "another_field": { 
         "$ref": "#/definitions/lang" 
      } 
   } 
}

Several properties can then reference the same definition of the language, avoiding repetitions. We can now define a JSON Schema for the catalog section of our e-commerce web site:

{
   "$schema": "http://json-schema.org/draft-04/schema#", 
   "type": "object", 
   "title": "Online Shop", 
   "description": "The coolest e-commerce web site around.", 
   "definitions": { 
      "lang": { 
         "title": "Language", 
         "description": "Language of the user interface.", 
         "enum": [ 
            "en", "fr", "es" 
         ] 
      }, 
      "category": { 
         "title": "Category", 
         "description": "Music category.", 
         "enum": [ 
            "pop", "rock", "jazz" 
         ] 
      } 
   },
   "properties": { 
      "catalog": { 
         "type": "object", 
         "title": "Catalog", 
         "description": "Full list of available items.", 
         "properties": { 
            "language": { 
               "$ref": "#/definitions/lang" 
            }, 
            "category": { 
               "$ref": "#/definitions/category" 
            }, 
            "items_table": { 
               "type": "object", 
               "title": "Items table", 
               "description": "Table displaying available items.", 
               "properties": { 
                  "items_per_page": { 
                  "type": "integer", 
                  "title": "Item per page", 
                  "description": "Number of items to be displayed for each page.", 
                  "default": 25 
               }, 
            } 
         } 
      } 
   } 
} 

 

 

From the schema to the JSON

We have defined the schema that describes our application. We need now to generate a JSON file, containing the default values, to be used to configure the whole application. The best solution to do so is to use Grunt in combination with the json-schema-defaults NPM package. It is possible to define a simple task as follows:


'use strict'; 

module.exports = function (grunt) { 

   /* Initiate Grunt configuration. */ 
   grunt.initConfig({ 
      pkg: grunt.file.readJSON('package.json') 
   }); 

   /* Create configuration file. */ 
   grunt.registerTask('default', 'Create JSON configuration file from JSON Schema.', function() { 

      /* Load JSON Schema utilities. */ 
      var defaults = require('json-schema-defaults'); 

      /* Load JSON Schema. */ 
      var schema = grunt.file.readJSON('config/schema.json', [, { encoding: 'utf8' }]); 

      /* Create JSON. */ 
      var json = defaults(schema); 

      /* Write JSON. */ 
      grunt.file.write('config/my_settings.json', JSON.stringify(json), [, {encoding: 'utf8'}]); 

   }); 

};

This task generates the my_settings file that we can use to configure our application. Each property of this file corresponds to a module in our architecture, and its children correspond to the submodules. The main module will then have to propagate the right piece of JSON to the submodule, and the submodule is in charge to propagate the configuration to its children, and so forth. As mentioned before, each module has its own configuration object. Such object is integrated with the settings stored in JSON file through the JQuery $.extend:


this.CONFIG = $.extend(true, {}, this.CONFIG, JSONConfig); 

Conclusions

JSON Schema is a standard format to describe JSON files. This standard is suitable to describe the architecture of a multimodules application, maintaining the hierarchy of the components. Each module, and its children, can be defined in a standard way. JSON Schema also allows you to define default values and common properties that can be reused in the all file. It is possible to generate a JSON file from a valid JSON Schema through the use of Grunt. The resulting file contains the default settings for all the modules and submodules of the application. The schema has to define only the properties that we want to override, because each module has its own default settings embedded in the JavaScript code. It is then possible to configure all the components of a complex application (e.g. REST URL’s, language, etc.) by editing a single file, the JSON schema, that will generate the configuration object that will be propagated to all the modules.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s