Skip to content

binhqx/jsonTables

Repository files navigation

jsonTables

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.

Features

  • 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

Installation

npm install

Usage

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

Linking related PLM entities with foreign keys

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);
}

Development

Run the unit tests:

npm test

Type-check the source files:

npm run typecheck

Storybook demo

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.

JSON Schema type gallery

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.

Basic person record

{
  "$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.

Arrays of things

{
  "$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.

Enumerated values

{
  "$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.

Regular expression pattern

{
  "$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.

Complex object with nested properties

{
  "$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.

Conditional validation with dependentRequired

{
  "$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.

Conditional validation with dependentSchemas

{
  "$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.

Conditional validation with if/then/else

{
  "$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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •