Skip to content
Tobias Munk edited this page Oct 27, 2022 · 6 revisions

This wiki is intended for people who want to understand JSON Editor's code structure in order to hack, contribute to, or customize the code. If you simply want to use JSON Editor as is, the README should suffice.

This document assumes a basic knowledge of JSON Schema.

This document is a work in progress. More will be added soon.

Overall Structure

JSON Editor is built to be extremely modular. This section will go over each of the modular components and how they work together.

Intro and Outro (src/intro.js and src/outro.js)

These files wrap everything in a closure to keep things self contained.

In addition, intro.js has licence information in a comment at the top along with the current version number and date. JSON Editor uses Semantic Versioning and is in the pre-1.0 phase. This version number is incremented whenever code is pushed to github. The minor version is incremented when major new features or backwards compatibility breaks are added. Otherwise, the patch version is incremented.

Class Inheritance (src/class.js)

JSON Editor uses John Resig's Simple Javascript Inheritance model (http://ejohn.org/blog/simple-javascript-inheritance/). I don't recommend using this for complicated inheritance structures, but it fits JSON Editor's use case perfectly.

var MyParentClass = Class.extend({
  method1: function() {},
  method2: function() {}
});

var MyChildClass = MyParentClass.extend({
  method1: function() {
    this._super();
  },
  method3: function() {}
});

Every part of JSON Editor is a class. This makes it easy to extend parts as needed without modifying the core code directly.

Utilities (src/utilities.js)

JSON Editor was originally built as a jQuery plugin. When the jQuery dependency was removed, a couple utility functions were added in its place.

  • $extend - Just like jQuery.extend, except it is recursive by default.
  • $each - Identical behavior to jQuery.each
  • $trigger - Trigger a native JavaScript event
  • $triggerc - Trigger a custom JavaScript event

IE9 (src/ie9.js)

This file contains polyfills for old browsers. Currently, there are only 3 polyfills:

  • requestAnimationFrame
  • CustomEvent constructor
  • Array.isArray

Validator (src/validator.js)

The validator is responsible for validating JSON against a JSON Schema and returning a list of errors. The validator is run automatically every time the editor's value changes.

Validation results are used a number of different places. The obvious ones are for displaying errors inline in the rendered form and powering the validate api call. Another not-so-obvious use of the validation results is to determine which oneOf schema to use when rendering part of the form. Consider the following JSON Schema:

{
  "type": "object",
  "oneOf": [
    {
      "properties": {
        "name": {"type": "string"}
      },
      "required": ["name"],
      "additionalProperties": false
    },
    {
      "properties": {
        "id": {"type": "integer"}
      },
      "required": ["id"],
      "additionalProperties": false
    }
  ]
}

When the value of the form is set programatically, JSON Editor validates the value against each of the schemas and uses the first one that passes to render the form. A similar thing is done for compound types (e.g. { "type": ["integer","string"] }).

The Validator code itself is very complex. It starts with the outermost schema and recursively descends to child schemas, validating along the way. All of the JSON Schema Draft 3 and Draft 4 keywords are supported.

If there is a validation error, a simple error object is added to an array. At the end of validation, this array of errors is returned. An empty array is returned if there are no validation errors. Each error object in the array has 3 properties, path, property, and message.

{
  "path": "root.person.first_name",
  "property": "minLength",
  "message": "Value must be at least 2 characters long"
}

At each step in the recursive descent, JSON Editor will also call any custom validation functions defined in JSONEditor.defaults.custom_validators.

IconLib (src/iconlib.js)

JSON Editor supports multiple icon libraries. These are used to add icons to buttons.

All icon libraries extend JSONEditor.AbstractIconLib. AbstractIconLib is set up to make it really easy to add new icon library support. Here's the code for FontAwesome4 support (src/iconlibs/fontawesome4.js):

JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({
  mapping: {
    collapse: 'caret-square-o-down',
    expand: 'caret-square-o-right',
    delete: 'times',
    edit: 'pencil',
    add: 'plus',
    cancel: 'ban',
    save: 'save',
    moveup: 'arrow-up',
    movedown: 'arrow-down'
  },
  icon_prefix: 'fa fa-'
});

Calling getIcon("cancel") will return the DOM element <i class="fa fa-ban"></i>. If you want to add an icon library that doesn't conform to this style, you can override the getIcon method and handle the DOM creation yourself.

Theme (src/theme.js)

Themes handle DOM creation, layout, and styling of JSON Editor forms. All themes extend the JSONEditor.AbstractTheme class.

There are a handful of themes provided by default and it's fairly easy to create your own. The methods in the abstract class return sensible default DOM node structures, so in most cases you can just do something like this:

JSONEditor.defaults.themes.mytheme = JSONEditor.AbstractTheme.extend({
  getTable: function() {
    // Base function creates an empty <table> DOM element
    var el = this._super();
    
    // Modify this base element
    el.style.fontSize = '50px';

    return el;
  }
});

Template (src/template.js)

A "template" in JSON Editor refers to javascript template engines like Mustache or Handlebars. Templates are objects with a compile method that returns a compiled template function. This function takes 2 a single view parameter containing variables and returns a string. Here is a barebones example:

var mytemplate = {
  compile: function(template_string) {
    return function(view) {
      return template_string + ' (' + JSON.stringify(view) + ')';
    }
  }
};

Templates are used in a number of different places:

  • The template keyword for schemas of type string
  • The headerTemplate schema keyword
  • The enumSource.filter, enumSource.title, and enumSource.value schema keywords

Editor (src/editor.js)

This file defines the base class for all editors - JSONEditor.AbstractEditor. An editor is a class that provides an editing interface for a schema. For example, a boolean editor may render a checkbox and label. Some editors, like those for objects and arrays, contain child editors. This recursive structure allows JSON Editor to support arbitrary levels of nesting in a JSON Schema.

JSONEditor.defaults.resolvers contains an array or resolver functions for determining which editor to use for a particular schema. If a resolver function matches, it returns the name of the editor, otherwise it returns null and the next resolver function is checked. For example, there is a resolver function that checks for {"type":"boolean"} and returns the boolean editor if it matches.

The AbstractEditor class defines many methods which can be extended by child classes. These are explained below.

editor.preBuild()

This is called right after instantiating the editor. You can put any setup code here, but do not create any DOM elements. For example, you can parse the schema options, setup your data structures, instantiate and pre-build child editors, etc.

This function has access to the following object properties:

  • this.schema - A copy of the JSON Schema for this editor
  • this.options - Editor options (from the schema or set globally)
  • this.jsoneditor - A reference to the root JSONEditor instance.
  • this.parent - A reference to the parent editor or null in the case of the root.
  • this.key - The key of this field. For example, in {"properties": { "name": {"type": "string"}}}, the key for the string editor would be name. The key is "root" for the root editor.
  • this.path - A dot separated path to the current editor (e.g. "root.person.name"). The last segment is the same as this.key.
  • this.formname - The editor path in a form-friendly format (e.g. root[person][name]).

editor.build()

This is where you create DOM elements and render them to the screen. This function has access to all the properties in preBuild plus:

  • this.container - The containing DOM element for the editor.
  • this.theme - An instance of a theme class (i.e. one extending JSONEditor.AbstractTheme). Use this to create DOM structures.

When the user changes the value of the editor (e.g. by checking a checkbox), it needs to alert JSON Editor by calling this.onChange(true);. You also probably want to store this value by calling this.refreshValue().

editor.postBuild()

This is called immediately after build and is usually used to setup event listeners and do any last minute cleanup.

The abstract postBuild function has some default functionality, so make sure to call this._super() if you decide to override it.

editor.setValue(value, initial=false)

This is called when the editor's value is changed programatically. It should update the UI to reflect the new value. For example, calling setValue(false) on a boolean editor should uncheck the checkbox.

The initial parameter is set to true when setting the initial (or default) value of the editor.

This function should call this.refreshValue() when done.

editor.refreshValue()

This function should build the JSON value for the editor and store it in this.value. For example, a simple string editor could do this.value = this.input.value;

*note - this.value is what editor.getValue() returns

editor.destroy()

This should destroy all DOM elements and event listeners, delete all variables, and clean up any memory used. The abstract function handles some of the common destroy tasks, so you should call this._super() at the end if you decide to extend this method.

Other methods

Documentation for the other methods is coming soon:

  • setupWatchListeners()
  • onWatchedFieldChange()
  • showValidationErrors(errors)
  • enable()
  • disable()
  • getNumColumns()
  • register()
  • unregister()
  • addLinks()
  • updateHeaderText()

Core (src/core.js)

The core JSON Editor code provides some utility functions and glues all the other modular components together.

The JSONEditor.expandSchema utility function resolves any $ref, allOf, and extends keywords. These keywords are flattened by merging the referenced schema into the parent one.

This:

{
  "type": "string",
  "allOf": [
    { "minLength": 3 },
    { "maxLength": 10 }
  ]
}

is flattened to this:

{
  "type": "string",
  "minLength": 3,
  "maxLength": 10
}

This pre-processing step greatly reduces the complexity of the rest of the code.

Expanding schemas is the only asynchronous part of JSON Editor. This is to allow for loading external $ref URLs via ajax.

More documentation for core.js coming soon.

Defaults (src/defaults.js)

This file sets default options and makes it possible to use JSON Editor without any configuration if desired. Any of the defaults can be overridden after JSON Editor is loaded on the page.