@loaders.gl/schema
Version:
Table format APIs for JSON, CSV, etc...
101 lines (100 loc) • 3.32 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { getDataTypeFromArray, getDataTypeFromValue } from "./data-type.js";
/**
* SCHEMA SUPPORT - AUTODEDUCTION
* @param {*} table
* @param {*} schema
* @returns
*/
export function deduceTableSchema(table) {
switch (table.shape) {
case 'array-row-table':
case 'object-row-table':
return deduceSchemaFromRows(table.data);
case 'geojson-table':
return deduceSchemaFromGeoJSON(table.features);
case 'columnar-table':
return deduceSchemaFromColumns(table.data);
case 'arrow-table':
default:
throw new Error('Deduce schema');
}
}
export function deduceSchema(data) {
return Array.isArray(data) ? deduceSchemaFromRows(data) : deduceSchemaFromColumns(data);
}
/** Given an object with columnar arrays, try to deduce a schema */
function deduceSchemaFromColumns(columnarTable) {
const fields = [];
for (const [columnName, column] of Object.entries(columnarTable)) {
const field = deduceFieldFromColumn(column, columnName);
fields.push(field);
}
return { fields, metadata: {} };
}
/** Given an array of rows, try to deduce a schema */
function deduceSchemaFromRows(rowTable) {
if (!rowTable.length) {
throw new Error('deduce from empty table');
}
const fields = [];
const row0 = rowTable[0];
// TODO - fields can be nullable, false detection...
// Could look at additional rows if nulls in first row
// TODO - if array, column names will be numbers
for (const [columnName, value] of Object.entries(row0)) {
fields.push(deduceFieldFromValue(value, columnName));
}
return { fields, metadata: {} };
}
/** Given a GeoJSON, try to deduce a schema */
function deduceSchemaFromGeoJSON(features) {
if (!features.length) {
throw new Error('deduce from empty table');
}
const fields = [];
const row0 = features[0].properties || {};
// TODO - fields can be nullable, false detection...
// Could look at additional rows if nulls in first row
// TODO - if array, column names will be numbers
for (const [columnName, value] of Object.entries(row0)) {
fields.push(deduceFieldFromValue(value, columnName));
}
return { fields, metadata: {} };
}
/** Given a column (i.e. array), attempt to deduce an appropriate `Field` */
function deduceFieldFromColumn(column, name) {
if (ArrayBuffer.isView(column)) {
const type = getDataTypeFromArray(column);
return {
name,
type: type.type || 'null',
nullable: type.nullable
// metadata: {}
};
}
if (Array.isArray(column) && column.length > 0) {
const value = column[0];
const type = getDataTypeFromValue(value);
// TODO - support nested schemas?
return {
name,
type,
nullable: true
// metadata: {},
};
}
throw new Error('empty table');
}
/** Given a value, attempt to deduce an appropriate `Field` */
function deduceFieldFromValue(value, name) {
const type = getDataTypeFromValue(value);
return {
name,
type,
nullable: true
// metadata: {}
};
}