Conversion between LinkAhead data models, JSON schema, and XLSX (and vice versa)
This file describes the conversion between JSON schema files and XLSX
templates, and between JSON data files following a given schema and XLSX
files with data. This conversion is handled by the Python modules in the
table_json_conversion
library.
Data models in JSON Schema and JSON data
Let’s start simple! If you would describe a Person
Record with the
Properties family_name
and given_name
in JSON, it would probably
look like this:
{
"Person":
{
"family_name": "Steve",
"given_name": "Stevie"
}
}
The data model in LinkAhead defines the types of records present in a LinkAhead instance and their structure. This data model can also be represented in a JSON Schema, which defines the structure of JSON files containing records pertaining to the data model.
You can define this kind of structure with the following JSON schema:
{
"type": "object",
"properties": {
"Person": {
"type": "object",
"properties": {
"family_name": {
"type": "string"
},
"given_name": {
"type": "string"
}
}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
The above schema (and schemas created by
json_schema_exporter.merge_schemas(...)
) is, from a broad view, a
dict with all the top level recordtypes (the recordtype names are the
keys). This is sufficient to describe the data model. However, actual
data often consists of multiple entries of the same type (e.g. multiple
Persons).
Since the data model schema does not match multiple data sets, there is a utility function which creates a data array schema out of the data model schema: It basically replaces the top-level entries of the data model by lists which may contain data.
For example, the following JSON describes two “Person” Records:
{
"Person": [
{
"family_name": "Steve",
"given_name": "Stevie"
},
{
"family_name": "Star",
"given_name": "Stella"
}
]
}
The JSON Schema for a JSON like the above one could look like the following:
{
"type": "object",
"properties": {
"Person": {
"type": "array",
"items": {
"type": "object",
"properties": {
"family_name": {
"type": "string"
},
"given_name": {
"type": "string"
}
}
}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
This would define that the top level object/dict may have a key
Person
which has as value an array of objects that in turn have the
properties family_name
and given_name
.
You can create a data array schema from a data model schema using
xlsx_utils.array_schema_from_model_schema
.
From JSON to XLSX: Data Representation
The following describes how JSON files representing LinkAhead records are converted into XLSX files, or how JSON files with records are created from XLSX files.
The attribute name (e.g., “Person” above) determines the RecordType, and the value of this attribute can either be an object or a list. If it is an object (as in the example above), a single record is represented. In the case of a list, multiple records sharing the same RecordType as the parent are represented.
The Properties of the record (e.g., family_name
and given_name
above) become columns in the XLSX file. Thus the XLSX file created
from the above example would have a sheet “Person” with the following
table:
given_name |
family_name |
---|---|
Stevie |
Steve |
Stella |
Star |
The properties of objects (Records) in the JSON have an attribute name and a value. The value can be:
A primitive (text, number, boolean, …)
A record
A list of primitive types
A list of unique enums (multiple choice)
A list of records
In cases a. and c., a cell is created in the column corresponding to the property in the XLSX file. In case b., columns are created for the Properties of the record, where for each of the Properties the cases a. - e. are considered recursively. Case d. leads to a number of columns, one for each of the possible choices.
For case e. however, the two-dimensional structure of an XLSX sheet is not sufficient. Therefore, for such cases, new XLSX sheets/tables are created.
In these sheets/tables, the referenced records are treated as described above (new columns for the Properties). However, there are now additional columns that indicate from which “external” record these records are referenced.
Let’s now consider these five cases in detail and with examples:
a. Properties with primitive data types
{
"Training": [
{
"date": "2023-01-01",
"url": "www.indiscale.com",
"duration": 1.0,
"participants": 1,
"remote": false
},
{
"date": "2023-06-15",
"url": "www.indiscale.com/next",
"duration": 2.5,
"participants": None,
"remote": true
}
]
}
This entry will be represented in an XLSX sheet with the following content:
date |
url |
duration |
participants |
remote |
---|---|---|---|---|
2023-01-01 |
www.indiscale.com |
1.0 |
1 |
false |
2023-06-15 |
www.indiscale.com/next |
2.5 |
true |
b. Property referencing a record
{
"Training": [
{
"date": "2023-01-01",
"supervisor": {
"family_name": "Stevenson",
"given_name": "Stevie"
}
}
]
}
This entry will be represented in an XLSX sheet named “Training” with the following content:
date |
supervisor.family_name |
supervisor.given_name |
---|---|---|
2023-01-01 |
Stevenson |
Stevie |
c. Properties containing lists of primitive data types
{
"Training": [
{
"url": "www.indiscale.com",
"subjects": ["Math", "Physics"],
}
]
}
This entry would be represented in an XLSX sheet with the following content:
url |
subjects |
---|---|
www.indiscale.com |
Math;Physics |
The list elements are written into the cell separated by ;
(semicolon). If the elements contain the separator ;
, it is escaped
with \
.
d. Multiple choice properties
{
"Training": [
{
"date": "2024-04-17",
"skills": [
"Planning",
"Evaluation"
]
}
]
}
If the skills
list is denoted as an enum
array with
"uniqueItems": true
in the json schema, this entry would be
represented like this in an XLSX:
date |
skills.Planning |
skills.Communication |
skills.Evaluation |
---|---|---|---|
2024-04-17 |
x |
x |
Note that this example assumes that the list of possible choices, as given in the json schema, was “Planning, Communication, Evaluation”.
e. Properties containing lists with references
{
"Training": [
{
"date": "2023-01-01",
"coach": [
{
"family_name": "Sky",
"given_name": "Max",
},
{
"family_name": "Sky",
"given_name": "Min",
}
]
}
]
}
Since the two coaches cannot be represented properly in a single cell, another worksheet is needed to contain the properties of the coaches.
The sheet for the Trainings in this example only contains the “date” column
date |
---|
2023-01-01 |
Additionally, there is another sheet where the coaches are stored. Here, it is crucial to define how the correct element is chosen from potentially multiple “Trainings”. In this case, it means that the “date” must be unique.
The second sheet looks like this:
date |
|
|
---|---|---|
2023-01-01 |
Sky |
Max |
2023-01-01 |
Sky |
Min |
Note: This uniqueness requirement is not strictly checked right now, it is your responsibility as a user that such “foreign properties” are truly unique.
When converting JSON files that contain Records that were exported from LinkAhead it might be a good idea to use the LinkAhead ID as a unique identifier for Records. However, if your Records do not yet have LinkAhead IDs you need to find some other identifying properties/foreign keys. Note, that those properties only need to identify a Record uniquely within the list of Records: In the above example the “coach” Record needs to be identified in the list of coaches.
User Interaction
The primary and most straight forward use case of this utility is to export LinkAhead data as JSON and then as XLSX tables. This can be done fully automatic.
TODO show how!
The hidden cells for automation are designed such that the XLSX template that is created can be customized such that it is a nicely formatted table. The hidden content must remain. See below for tips on how to manipulate the table.
The second use case is to use XLSX to collect data and then import it into LinkAhead. Here, it may be necessary to define foreign keys in order to identify Records in lists.
Table Manipulation
All formatting is ignored
Nothing has to be observed when adding new data rows
When adding new descriptory rows (for example one for descriptions of the columns), the
COL_TYPE
must be set toIGNORE
You can freely rename sheets.
You can freely rename columns (since the row containing the column names is set to
IGNROE
; the Property name is taken from the last path element)You can change the order of columns. However, you have to make sure to move the full column including hidden elements. Thus you should not select a range of cells, but click on the column index in your spread sheet program.
Note: Requirements
This conversion does not allow arbitrary JSON schema files nor does it support arbitrary JSON files since conversion to XLSX files would not make sense. Instead, this utility is tailored to supported conversion of data (models) that are structured like data (models) in LinkAhead:
The JSON schema describes a data model of RecordTypes and Properties as it would be generated by the caosadvancedtools.json_schema_exporter module.
The JSON files must contain arrays of Records complying with such a data model.
Thus, when converting from a JSON schema, the top level of the JSON schema must be a dict. The keys of the dict are RecordType names.
Current limitations
The current implementation still lacks the following:
Files handling is not implemented yet.