@naturalcycles/js-lib
Version:
Standard library for universal (browser + Node.js) javascript
88 lines (87 loc) • 2.31 kB
JavaScript
import { _uniq } from '../../array/index.js';
import { _stringMapEntries } from '../../types.js';
/**
* Each row must be an object (current limitation).
*
* `additionalProperties` is set to `true`, cause it's safer.
*/
export function generateJsonSchemaFromData(rows) {
return objectToJsonSchema(rows);
}
function objectToJsonSchema(rows) {
const typesByKey = {};
rows.forEach(r => {
Object.keys(r).forEach(key => {
typesByKey[key] ||= new Set();
typesByKey[key].add(getTypeOfValue(r[key]));
});
});
const s = {
type: 'object',
properties: {},
required: [],
additionalProperties: true,
};
_stringMapEntries(typesByKey).forEach(([key, types]) => {
const schema = mergeTypes([...types], rows.map(r => r[key]));
if (!schema)
return;
s.properties[key] = schema;
});
// console.log(typesByKey)
return s;
}
function mergeTypes(types, samples) {
// skip "undefined" types
types = types.filter(t => t !== 'undefined');
if (!types.length)
return undefined;
if (types.length > 1) {
// oneOf
const s = {
oneOf: types.map(type => mergeTypes([type], samples)),
};
return s;
}
const type = types[0];
if (type === 'null') {
return {
type: 'null',
};
}
if (type === 'boolean') {
return {
type: 'boolean',
};
}
if (type === 'string') {
return {
type: 'string',
};
}
if (type === 'number') {
return {
type: 'number',
};
}
if (type === 'object') {
return objectToJsonSchema(samples.filter((r) => r && typeof r === 'object'));
}
if (type === 'array') {
// possible feature: detect if it's a tuple
// currently assume no-tuple
const items = samples.filter(r => Array.isArray(r)).flat();
const itemTypes = _uniq(items.map(i => getTypeOfValue(i)));
return {
type: 'array',
items: mergeTypes(itemTypes, items),
};
}
}
function getTypeOfValue(v) {
if (v === null)
return 'null';
if (Array.isArray(v))
return 'array';
return typeof v;
}