logo json-db
  • Database
  • Docs
  • About

Creating a Schema Definition

The database structure is defined in a schema.json file containing one or more collections which each have on or more fields. Based on the schema.json file the user interface and constraints to manage the database is created.

schema.json

{
  "name": "My Database",
  "collections": [
    {...},
    {...}
  ],
  "config": {...}
}
  • name: {string} - The name of the database.
  • collections: {object[]} - One or more collection objects defining the database structure.
  • config: {object} [optional] - Additional configuration of the db.
  • config.out: {object} [optional] - Configuration of collection output files.
  • config.out.indentSize: {number} [optional] - The indentation size to use for formatting the json output. Set to null for faster saves and smaller file-sizes (default=2).
  • config.ui: {object} [optional] - Global configuration of the ui.
  • config.ui.manage: {object} [optional] - Global configuration of the manager for collections.
  • config.ui.manage.pageSize: {number} [optional] - The number of the entries to show in the manager.

collections

A schema definition must contain one or more collection mappings, where each collection mapping represents the prototype for the entries of the collection. This could e.g. be persons, books, games, cars etc.

{
  "name": {
    "singular": "person",
    "plural": "persons"
  },
  "fields": [
    {...},
    {...}
  ],
  "ui": {
    "manage": {
      "fields": ["fullName", "age", "address.street"]
    }
  }
}
  • name: {object} - The singular and plural names of the collection.
  • name.singular: {string} - The singular name of the collection.
  • name.plural: {string} - The plural name of the collection.
  • fields: {object[]} - One or more field objects defining the collection structure.
  • ui: {object} [optional] - Configuration of the editor ui for the collection.
  • ui.manage: {object} [optional] - Configuration of manager for the collection.
  • ui.manage.fields: {string[]} [optional] - The fields to show in the manager for the collection. If undefined all direct non-array fields will be shown.

fields

A collection can include one or more field mappings defining the fields of the collection.

All fields must have a type, propName and a title property.

Each field can describe a single value or an array of values using the array property. Additionally each field can have a set of options tied to the specific type of field, where some are required and some are optional.

text fields

Text fields are used for storing textual data such as names, adresses and descriptions.

A text field is stored as a JSON string.

{
  "type": "text",
  "propName": "fullName",
  "title": "Full Name",
  "array": false,
  "required": false,
  "textRows": 1,
  "defaultValue": "some default value"
}
  • type: {string} - The type of the field.
  • propName: {string} - The name of the property the value should be stored in.
  • title: {string} - The title of the field to show in the editor.
  • array: {boolean} [optional] - Defines the field to be an array of values if true which makes adding multiple values possible (default=false).
  • required: {boolean} [optional] - Sets the field to be required if true (default=false).
  • textRows: {number} [optional] - The number of text rows to display in the create/edit form (default=1).
  • defaultValue: {string|string[]} [optional] - A default value or an array of values if array=true (default=undefined).

number fields

Number fields are used for storing numeric data such as ages, counts and scores.

A number field is stored as a JSON number.

{
  "type": "number",
  "propName": "age",
  "title": "Age",
  "array": false,
  "required": false,
  "defaultValue": 0
}
  • type: {string} - The type of the field.
  • propName: {string} - The name of the property the value should be stored in.
  • title: {string} - The title of the field to show in the editor.
  • array: {boolean} [optional] - Defines the field to be an array of values if true which makes adding multiple values possible (default=false).
  • required: {boolean} [optional] - Sets the field to be required if true (default=false).
  • defaultValue: {number|number[]} [optional] - A default value or an array of values if array=true (default=undefined).

enum fields

Enum fields are used for storing any valid JSON data but restricted to a predefined set of the given type. The value would typically be a string for e.g. a persons civil status or a number for a fixed range rating, but objects and arrays are possible values as well.

An enum field is stored as the value defined in its options value property.

{
  "type": "enum",
  "propName": "civilStatus",
  "title": "Civil Status",
  "options": [
    { "value": "single", "title": "Single" },
    { "value": "relationship", "title": "In a relationship" },
    { "value": "married", "title": "Married" },
    { "value": "complicated", "title": "It's complicated" }
  ],
  "array": false,
  "required": false,
  "defaultValue": "single"
}
  • type: {string} - The type of the field.
  • propName: {string} - The name of the property the value should be stored in.
  • title: {string} - The title of the field to show in the editor.
  • options: {object[]} - The options available to choose from.
  • options: {object[].value} - The value to set if this option is selected.
  • options: {object[].title} - The title to display for this option.
  • array: {boolean} [optional] - Defines the field to be an array of values if true which makes adding multiple values possible (default=false).
  • required: {boolean} [optional] - Sets the field to be required if true (default=false).
  • defaultValue: {*} [optional] - The value of the option which should be default selected (default=undefined).

object fields

Object fields are used for storing related fields grouped together as an object such as adresses with street, number, city and zip-code, books with title, author and description or links with label, url and type.

An object field is stored as a JSON object with properties of the types defined in the fields section of the object field.

Object fields can themselves contain fields of the object type making nested hierarchies of objects possible. In most cases an object can as an alternative be modelled as relation if e.g. normalization of the collection's data is required.

{
  "type": "object",
  "propName": "address",
  "title": "Address",
  "fields": [
    {
      "type": "text",
      "propName": "street",
      "title": "Street"
    },
    {
      "type": "text",
      "propName": "city",
      "title": "City"
    }
  ],
  "array": false,
  "required": false
}
  • type: {string} - The type of the field.
  • propName: {string} - The name of the property the value should be stored in.
  • title: {string} - The title of the field to show in the editor.
  • fields: {object[]} - The fields for this object - all field types are allowed and should be mapped in the same way as fields defined in a collection.
  • array: {boolean} [optional] - Defines the field to be an array of values if true which makes adding multiple objects possible (default=false).
  • required: {boolean} [optional] - Sets the field to be required if true (default=false).

relation fields

Relation fields are used for mapping relations like in a relational database where primary and foreign keys represents a connection between two entries.

A relation field is stored as an integer or an array of integers referring to the primary key of the entry which the relation points to.

A relation is often a good alternative to nested objects if the object is a part of multiple relationships and the fields of the object might change over time. This is also referred to as normalization of the database, where the goal is to make sure that every piece of information only exists in one place which makes it easier to keep all data up to date when data is modified.

{
  "type": "relation",
  "propName": "bornIn",
  "title": "Country of Origin",
  "targetCollection": {
    "name": "countries",
    "titleField": "name"
  }
  "readOnly": false,
  "array": false,
  "required": false
}
  • type: {string} - The type of the field.
  • propName: {string} - The name of the property the value should be stored in.
  • title: {string} - The title of the field to show in the editor.
  • targetCollection: {object} - The mapping of the relationship.
  • targetCollection.name: {string} - The name of the target collection the relation points to.
  • targetCollection.titleField: {string|string[]} - The field or an array of fields from the targetCollection which should be shown in the editor. Nested paths, which does not include arrays, are allowed.
  • targetCollection.inversedByPropName: {string} [optional] - The property name of the target collection which points back to this collection. Use this for creating bidirectional relationships where updating the relationship on one side will automatically update the relationship of the other side and vice-versa.
  • readOnly: {boolean} [optional] - Set to true if the field should be read-only from this side of the relationship (default=false).
  • array: {boolean} [optional] - Defines the field to be an array of values if true which makes adding multiple relations possible (default=false).
  • required: {boolean} [optional] - Sets the field to be required if true (default=false).

For bidirectional relationships consider using readOnly on one of the sides. Often the reason for choosing a bidirectional relationship is because the data model becomes easier to use afterwards and not because it necessarily makes sense to edit the relationship from both sides - one of the sides is typically the primary initiator/owner of the relationship. When using readOnly on a relation you are still able to see the related entries but can only edit them from the other side.

{
  "type": "relation",
  "propName": "owner",
  "title": "Owner",
  "targetCollection": {
    "name": "persons",
    "titleField": "name"
  }
  "required": true
}
A owner field in a books collection
{
  "type": "relation",
  "propName": "books",
  "title": "Books",
  "targetCollection": {
    "name": "books",
    "titleField": "title",
    "inversedByPropName": "owner"
  }
  "readOnly": true,
  "array": true
}
A books field in a persons collection

bidirectional relations with inversedByPropName cannot have required set to true without having one of the sides readOnly set to true as the synchronization of the inverse side, when a relation can be edited from both sides, can result in violations of the required restriction.

E.g. in a person owns many books relationship where a book only can be owned by a single person and the ownership can be changed from both sides the required restriction will be violated if:

  • the book is removed from the person side (no new owner will be set for the book) - this would violate the required owner on the book side
  • the owner of a book is changed on the person- or the book side, and the previous owner only had a single book - this would violate the required books on the person side

Editing Existing JSON Documents

Json-db can be used to edit any json document as long as it defines a root object with an entries array and each entry has an unique integer id > 0.

{
  "entries": [
    { "id": 1, "prop1": "value1", "prop2": {...} },
    { "id": 4, "prop1": "value2", "prop2": {...} },
    { "id": 9, "prop1": "value3", "prop2": {...} },
  ]
}

Only the fields which needs to be edited is required to be specified in the schema definition. Properties not specified will be kept as is as long as they are not a sub-path of one of the specified fields.

Full Schema Definition Example

The following shows a full schema.json for a movie database. The full database with data can be downloaded from the demo databases section.

{
  "name": "Movie DB",
  "collections": [
    {
      "name": {
        "singular": "country",
        "plural": "countries"
      },
      "fields": [
        {
          "propName": "name",
          "title": "Name",
          "type": "text",
          "textRows": 1,
          "required": true
        },
        {
          "propName": "iso3166_1",
          "title": "ISO 3166-1 Country Code",
          "type": "text",
          "textRows": 1,
          "required": true
        },
        {
          "propName": "position",
          "title": "Position",
          "type": "object",
          "required": true,
          "fields": [
            {
              "propName": "lon",
              "title": "Longitude",
              "type": "number",
              "required": true
            },
            {
              "propName": "lat",
              "title": "Latitude",
              "type": "number",
              "required": true
            }
          ]
        }
      ]
    },
    {
      "name": {
        "singular": "person",
        "plural": "persons"
      },
      "fields": [
        {
          "propName": "name",
          "title": "Name",
          "type": "text",
          "textRows": 1,
          "required": true
        },
        {
          "propName": "gender",
          "title": "Gender",
          "type": "enum",
          "required": true,
          "options": [
            {
              "value": "female",
              "title": "Female"
            },
            [more entries ...]
          ]
        },
        {
          "propName": "popularity",
          "title": "Popularity",
          "type": "number",
          "required": true
        },
        {
          "propName": "knownForDepartment",
          "title": "Known For Department",
          "type": "enum",
          "required": true,
          "options": [
            {
              "value": "Acting",
              "title": "Acting"
            },
            [... more entries ...]
          ]
        },
        {
          "propName": "profilePath",
          "title": "Profile Url",
          "type": "text",
          "textRows": 1
        }
      ]
    },
    {
      "name": {
        "singular": "movie",
        "plural": "movies"
      },
      "fields": [
        {
          "propName": "title",
          "title": "Title",
          "type": "text",
          "required": true
        },
        {
          "propName": "description",
          "title": "Description",
          "type": "text",
          "required": true
        },
        {
          "propName": "tagline",
          "title": "Tagline",
          "type": "text"
        },
        {
          "propName": "language",
          "title": "Language",
          "type": "enum",
          "options": [
            {
              "value": "en",
              "title": "en"
            },
            [... more entries ...]
          ],
          "required": true
        },
        {
          "propName": "runtime",
          "title": "Runtime",
          "type": "number",
          "required": true
        },
        {
          "propName": "genres",
          "title": "Genres",
          "type": "enum",
          "options": [
            {
              "value": "Action",
              "title": "Action"
            },
            [... more entries ...]
          ],
          "required": true
        },
        {
          "propName": "voteCount",
          "title": "Vote Count",
          "type": "number",
          "required": true,
          "defaultValue": 0
        },
        {
          "propName": "voteAverage",
          "title": "Vote Average",
          "type": "number",
          "required": true,
          "defaultValue": 0
        },
        {
          "propName": "releaseDate",
          "title": "Release Date",
          "type": "text",
          "required": true
        },
        {
          "propName": "posterPath",
          "title": "Poster Path URL",
          "type": "text"
        },
        {
          "propName": "productionCountry",
          "title": "Production Country",
          "type": "relation",
          "targetCollection": {
            "name": "countries",
            "titleField": "name"
          }
        },
        {
          "propName": "directors",
          "title": "Directors",
          "type": "relation",
          "array": true,
          "required": true,
          "targetCollection": {
            "name": "persons",
            "titleField": "name"
          }
        },
        {
          "propName": "cast",
          "title": "Cast",
          "type": "object",
          "array": "true",
          "fields": [
            {
              "propName": "character",
              "title": "Character",
              "type": "string",
              "required": true
            },
            {
              "propName": "actor",
              "title": "Actor",
              "type": "relation",
              "required": true,
              "targetCollection": {
                "name": "persons",
                "titleField": "name"
              }
            }
          ]
        }
      ]
    }
  ]
}
Created by chc.au.dk