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.
{
"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.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.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
fieldsText 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
).ui: {object} [optional]
- Configuration of the editor ui for the field.number
fieldsNumber 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
).ui: {object} [optional]
- Configuration of the editor ui for the field.enum
fieldsEnum 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
).inline: {boolean} [optional]
- Show selected options inline instead of blocks when array=true
(default=false
).ui: {object} [optional]
- Configuration of the editor ui for the field.object
fieldsObject 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
).ui: {object} [optional]
- Configuration of the editor ui for the field.relation
fieldsRelation 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
).ui: {object} [optional]
- Configuration of the editor ui for the field.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
}
{
"type": "relation",
"propName": "books",
"title": "Books",
"targetCollection": {
"name": "books",
"titleField": "title",
"inversedByPropName": "owner"
}
"readOnly": true,
"array": true
}
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:
required
owner on the book siderequired
books on the person sidefile
fieldsFile fields are used attaching files, e.g., images, text-documents, csv-files, to an entry.
A file
field is stored as an JSON object with details about the file (see example below).
{
"type": "file",
"propName": "profileImage",
"title": "Profile Image",
"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.required: {boolean} [optional]
- Sets the field to be required if true
(default=false
).ui: {object} [optional]
- Configuration of the editor ui for the field.The stored file object has the following properties:
name: {string}
- The internal unique name of the file.originalName: {string}
- The original name of the file.ext: {string}
- The file extension.mimeType: string
- The files mime type.size: number
- The size of the file in bytes.lastModified: number
- The time in milliseconds since Epoch the file was created in the database.{
"name": "23dj3.txt",
"originalName": "example.txt",
"ext": "txt",
"mimeType": "text/plain",
"size": 123,
"lastModified": 1754643507643
}
The actual file is copied to the database directory /resources
and renamed to a unique internal name.
See the Books Demo Database for an example.
Optionally fields can configure how they should be displayed in the editor with the following options:
The sorting options will be applied to array fields in the editor in the following views:
readOnly
arrays in the edit entry form.Furthermore, editable arrays will have a "sort" button () in the create- and edit entry form, that will apply the sorting to the array. The sorted order will be saved when the entry is saved.
{
"type": "object",
"propName": "authors",
"title": "Authors",
"array": true,
"ui": {
"sorting": [
{"field": "lastName", "order": "asc"]},
{"field": "firstName", "order": "asc"]}
]
}
}
ui: {object} [optional]
- The root of the ui configuration for the field.ui.sorting: {array} [optional]
- An array of sorting options. The first element is the primary sorting.ui.sorting[].field: {string} [required]
- The field to sort on. The path is relative to the owning field.
If the owning field has one the the types text
, number
or enum
the sorting field must be ""
(empty
string).ui.sorting[].order {'asc'|'desc'} [required]
The order to sort by.file
FieldsFields of type file
, supports sorting on the file object properties: originalName
, ext
and mimeType
.
{
"type": "file",
"propName": "attachments",
"title": "Attachments",
"array": true,
"ui": {
"sorting": [
{ "field": "originalName", "order": "asc" }
]
}
}
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.
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"
}
}
]
}
]
}
]
}