jsonTables
provides utilities that turn JSON Schema definitions into ag-Grid column and grid configurations. It is inspired by the JSON Forms renderer concept and is especially useful when you want to render arrays of JSON objects inside a spreadsheet-like editing experience. The toolkit is designed with Product Lifecycle Management (PLM) teams in mind so product data stewards can curate stage, compliance, and launch information without leaving a grid view.
- Generate ag-Grid column definitions directly from JSON Schema metadata
- Interpret JSON Forms UI schema options for column level customisation
- Respect schema attributes such as
enum
,readOnly
, and common string/number formats - Model relational links with UI schema
foreignKey
options so grids surface labelled selects while storing opaque identifiers - Provide reusable validation helpers powered by Ajv
npm install
import { generateGridConfig } from 'json-tables';
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
sku: { type: 'string', title: 'SKU' },
productName: { type: 'string', title: 'Product Name' },
lifecycleStage: {
type: 'string',
enum: ['Concept', 'Design', 'Production', 'In Market', 'Retired'],
default: 'Concept',
},
nextGateReview: {
type: 'string',
format: 'date',
title: 'Next Gate Review',
},
requiresComplianceAudit: { type: 'boolean', default: false },
},
required: ['sku', 'productName', 'lifecycleStage'],
},
};
const uiSchema = {
type: 'VerticalLayout',
options: {
grid: {
paginationPageSize: 25,
},
},
elements: [
{
type: 'Control',
scope: '#/properties/sku',
label: 'Lifecycle SKU',
options: {
column: {
width: 160,
},
},
},
{
type: 'Control',
scope: '#/properties/lifecycleStage',
},
{
type: 'Control',
scope: '#/properties/nextGateReview',
label: 'Gate Review',
},
{
type: 'Control',
scope: '#/properties/requiresComplianceAudit',
label: 'Needs Compliance Audit',
},
],
};
const data = [
{
sku: 'ALP-001',
productName: 'Alpine Smart Thermostat',
lifecycleStage: 'Design',
nextGateReview: '2024-07-15',
requiresComplianceAudit: true,
},
{
sku: 'SOL-210',
productName: 'SolarCharge Pro Inverter',
lifecycleStage: 'Production',
nextGateReview: '2024-09-01',
requiresComplianceAudit: false,
},
];
const config = generateGridConfig(schema, uiSchema, data);
// config.columnDefs -> ag-Grid column definitions
// config.rowData -> the row data array supplied to ag-Grid
// config.defaultColDef -> baseline column options (sortable, filterable, etc.)
// config.gridOptions -> top-level grid options extracted from the UI schema
PLM workflows frequently connect parent products to reusable assets such as
variations or bills of materials. Use UI schema foreignKey
options to expose a
labelled dropdown while persisting the original opaque identifier:
const relationshipUiSchema = {
type: 'VerticalLayout',
elements: [
{
type: 'Control',
scope: '#/properties/variationId',
options: {
foreignKey: {
relatedEntity: 'productVariation',
options: [
{ value: 'VAR-SMALL', label: 'Small' },
{ value: 'VAR-MEDIUM', label: 'Medium' },
{ value: 'VAR-LARGE', label: 'Large' },
],
},
},
},
{
type: 'Control',
scope: '#/properties/billOfMaterialsId',
options: {
foreignKey: {
relatedEntity: 'billOfMaterials',
options: [
{ value: 'BOM-BASELINE', label: 'Baseline Stitch Plan' },
{ value: 'BOM-THERMAL', label: 'Thermal Lining Assembly' },
],
allowUnlinked: true,
},
},
},
],
};
The generator will configure agSelectCellEditor
for those columns, reuse the
labels in filters, and keep the stored value aligned with integration
contracts.
Validate edited data against the source schema:
import { validateGridData } from 'json-tables';
const result = validateGridData(config.rowData, schema);
if (!result.valid) {
console.error(result.errors);
}
Run the unit tests:
npm test
Type-check the source files:
npm run typecheck
An interactive demonstration of the renderer utilities is available via Storybook. The story renders an ag-Grid instance backed by generateGridConfig
, making it easy to experiment with sorting, editing and selection behaviour during development.
npm run storybook
Running the command above will start Storybook at http://localhost:6006. Use it to validate UX changes without needing to integrate the utilities into a host application first.
Storybook also includes a JSON Schema Types Gallery that mirrors common schema constructs PLM teams encounter while shaping product data models. The gallery reuses the schemas below so you can see how each keyword translates into grid columns and editing affordances:
The Product Relationship Planner story demonstrates how foreignKey
options let merchandisers link child SKUs to parent products, variations, and shared bills of material from a single grid.
{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"firstName": { "type": "string" },
"lastName": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
}
}
The resulting grid showcases baseline text and number editors, reflecting how product steward profiles can be curated alongside lifecycle data.
{
"$id": "https://example.com/arrays.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Arrays",
"type": "object",
"properties": {
"fruits": { "type": "array", "items": { "type": "string" } },
"vegetables": { "type": "array", "items": { "$ref": "#/$defs/veggie" } }
},
"$defs": {
"veggie": {
"type": "object",
"required": ["veggieName", "veggieLike"],
"properties": {
"veggieName": { "type": "string" },
"veggieLike": { "type": "boolean" }
}
}
}
}
This example highlights how list-valued attributes appear in the grid, enabling product teams to capture qualitative research alongside structured lifecycle metrics.
{
"$id": "https://example.com/enumerated-values.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Enumerated Values",
"type": "object",
"properties": {
"data": { "enum": [42, true, "hello", null, [1, 2, 3]] }
}
}
Enumerations become dropdown editors paired with equality-focused text filters—ideal for lifecycle stages, compliance statuses, or other controlled vocabularies your PLM governance board maintains.
{
"$id": "https://example.com/regex-pattern.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Regular Expression Pattern",
"type": "object",
"properties": {
"code": { "type": "string", "pattern": "^[A-Z]{3}-\\\d{3}$" }
}
}
Pattern keywords ensure identifiers such as regional SKUs follow established naming conventions before they advance to launch gates.
{
"$id": "https://example.com/complex-object.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Complex Object",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"postalCode": { "type": "string", "pattern": "\\d{5}" }
},
"required": ["street", "city", "state", "postalCode"]
},
"hobbies": { "type": "array", "items": { "type": "string" } }
},
"required": ["name", "age"]
}
Nested object structures flatten into readable columns so analysts can correlate launch contacts, addresses, and interest groups without diving into raw JSON.
{
"$id": "https://example.com/conditional-validation-dependentRequired.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Conditional Validation with dependentRequired",
"type": "object",
"properties": {
"foo": { "type": "boolean" },
"bar": { "type": "string" }
},
"dependentRequired": {
"foo": ["bar"]
}
}
Use this pattern when toggling a boolean—such as "Requires sustainability audit"—should prompt additional commentary from the product steward.
{
"$id": "https://example.com/conditional-validation-dependentSchemas.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Conditional Validation with dependentSchemas",
"type": "object",
"properties": {
"foo": { "type": "boolean" },
"propertiesCount": { "type": "integer", "minimum": 0 }
},
"dependentSchemas": {
"foo": {
"required": ["propertiesCount"],
"properties": {
"propertiesCount": { "minimum": 7 }
}
}
}
}
Once a feature flag is active—"foo" in this case—the dependent schema enforces richer validation so the grid surfaces issues before records reach downstream systems.
{
"$id": "https://example.com/conditional-validation-if-else.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Conditional Validation with If-Else",
"type": "object",
"properties": {
"isMember": { "type": "boolean" },
"membershipNumber": { "type": "string" }
},
"required": ["isMember"],
"if": {
"properties": {
"isMember": { "const": true }
}
},
"then": {
"properties": {
"membershipNumber": { "type": "string", "minLength": 10, "maxLength": 10 }
}
},
"else": {
"properties": {
"membershipNumber": { "type": "string", "minLength": 15 }
}
}
}
The conditional story shows how the grid respects rule changes when stakeholders have different membership models—critical for launch partner and certification workflows.