UNPKG

genson-js

Version:

JSON schema generator with a possibility to merge schemas

247 lines 8.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createCompoundSchema = exports.extendSchema = exports.mergeSchemas = exports.createSchema = exports.wrapAnyOfSchema = exports.unwrapSchemas = exports.unwrapSchema = void 0; const types_1 = require("./types"); function createSchemaFor(value, options) { switch (typeof value) { case 'number': if (Number.isInteger(value)) { return { type: types_1.ValueType.Integer }; } return { type: types_1.ValueType.Number }; case 'boolean': return { type: types_1.ValueType.Boolean }; case 'string': return { type: types_1.ValueType.String }; case 'object': if (value === null) { return { type: types_1.ValueType.Null }; } if (Array.isArray(value)) { return createSchemaForArray(value, options); } return createSchemaForObject(value, options); } } function createSchemaForArray(arr, options) { if (arr.length === 0) { return { type: types_1.ValueType.Array }; } const elementSchemas = arr.map((value) => createSchemaFor(value, options)); const items = combineSchemas(elementSchemas); return { type: types_1.ValueType.Array, items }; } function createSchemaForObject(obj, options) { const keys = Object.keys(obj); if (keys.length === 0) { return { type: types_1.ValueType.Object, }; } const properties = Object.entries(obj).reduce((props, [key, val]) => { props[key] = createSchemaFor(val, options); return props; }, {}); const schema = { type: types_1.ValueType.Object, properties }; if (!(options === null || options === void 0 ? void 0 : options.noRequired)) { schema.required = keys; } return schema; } function combineSchemas(schemas, options) { const schemasByType = { [types_1.ValueType.Null]: [], [types_1.ValueType.Boolean]: [], [types_1.ValueType.Integer]: [], [types_1.ValueType.Number]: [], [types_1.ValueType.String]: [], [types_1.ValueType.Array]: [], [types_1.ValueType.Object]: [], }; const unwrappedSchemas = unwrapSchemas(schemas); for (const unwrappedSchema of unwrappedSchemas) { const type = unwrappedSchema.type; if (schemasByType[type].length === 0 || isContainerSchema(unwrappedSchema)) { schemasByType[type].push(unwrappedSchema); } else { continue; } } const resultSchemasByType = { [types_1.ValueType.Null]: schemasByType[types_1.ValueType.Null][0], [types_1.ValueType.Boolean]: schemasByType[types_1.ValueType.Boolean][0], [types_1.ValueType.Number]: schemasByType[types_1.ValueType.Number][0], [types_1.ValueType.Integer]: schemasByType[types_1.ValueType.Integer][0], [types_1.ValueType.String]: schemasByType[types_1.ValueType.String][0], [types_1.ValueType.Array]: combineArraySchemas(schemasByType[types_1.ValueType.Array]), [types_1.ValueType.Object]: combineObjectSchemas(schemasByType[types_1.ValueType.Object], options), }; if (resultSchemasByType[types_1.ValueType.Number]) { // if at least one value is float, others can be floats too delete resultSchemasByType[types_1.ValueType.Integer]; } const schemasFound = Object.values(resultSchemasByType).filter(Boolean); const multiType = schemasFound.length > 1; if (multiType) { const wrapped = wrapAnyOfSchema({ anyOf: schemasFound }); return wrapped; } return schemasFound[0]; } function combineArraySchemas(schemas) { if (!schemas || schemas.length === 0) { return undefined; } const itemSchemas = []; for (const schema of schemas) { if (!schema.items) continue; const unwrappedSchemas = unwrapSchema(schema.items); itemSchemas.push(...unwrappedSchemas); } if (itemSchemas.length === 0) { return { type: types_1.ValueType.Array, }; } const items = combineSchemas(itemSchemas); return { type: types_1.ValueType.Array, items, }; } function combineObjectSchemas(schemas, options) { if (!schemas || schemas.length === 0) { return undefined; } const allPropSchemas = schemas.map((s) => s.properties).filter(Boolean); const schemasByProp = Object.create(null); // const schemasByProp: Record<string, Schema[]> = {}; for (const propSchemas of allPropSchemas) { for (const [prop, schema] of Object.entries(propSchemas)) { if (!schemasByProp[prop]) { schemasByProp[prop] = []; } const unwrappedSchemas = unwrapSchema(schema); schemasByProp[prop].push(...unwrappedSchemas); } } const properties = Object.entries(schemasByProp).reduce((props, [prop, schemas]) => { if (schemas.length === 1) { props[prop] = schemas[0]; } else { props[prop] = combineSchemas(schemas); } return props; }, {}); const combinedSchema = { type: types_1.ValueType.Object }; if (Object.keys(properties).length > 0) { combinedSchema.properties = properties; } if (!(options === null || options === void 0 ? void 0 : options.noRequired)) { const required = intersection(schemas.map((s) => s.required || [])); if (required.length > 0) { combinedSchema.required = required; } } return combinedSchema; } function unwrapSchema(schema) { if (!schema) return []; if (schema.anyOf) { return unwrapSchemas(schema.anyOf); } if (Array.isArray(schema.type)) { return schema.type.map((x) => ({ type: x })); } return [schema]; } exports.unwrapSchema = unwrapSchema; function unwrapSchemas(schemas) { if (!schemas || schemas.length === 0) return []; const unwrappedSchemas = schemas.flatMap((schema) => unwrapSchema(schema)); return unwrappedSchemas; } exports.unwrapSchemas = unwrapSchemas; function wrapAnyOfSchema(schema) { const simpleSchemas = []; const complexSchemas = []; for (const subSchema of schema.anyOf) { if (Array.isArray(subSchema.type)) { simpleSchemas.push(...subSchema.type); } else if (isSimpleSchema(subSchema)) { simpleSchemas.push(subSchema.type); } else { complexSchemas.push(subSchema); } } if (complexSchemas.length === 0) { return { type: simpleSchemas }; } const anyOf = []; if (simpleSchemas.length > 0) { anyOf.push({ type: simpleSchemas.length > 1 ? simpleSchemas : simpleSchemas[0] }); } anyOf.push(...complexSchemas); return { anyOf }; } exports.wrapAnyOfSchema = wrapAnyOfSchema; function intersection(valuesArr) { if (valuesArr.length === 0) return []; const arrays = valuesArr.filter(Array.isArray); const counter = {}; for (const arr of arrays) { for (const val of arr) { if (!counter[val]) { counter[val] = 1; } else { counter[val]++; } } } const result = Object.entries(counter) .filter(([_, value]) => value === arrays.length) .map(([key]) => key); return result; } function isSimpleSchema(schema) { const keys = Object.keys(schema); return keys.length === 1 && keys[0] === 'type'; } function isContainerSchema(schema) { const type = schema.type; return type === types_1.ValueType.Array || type === types_1.ValueType.Object; } // FACADE function createSchema(value, options) { if (typeof value === 'undefined') value = null; const clone = JSON.parse(JSON.stringify(value)); return createSchemaFor(clone, options); } exports.createSchema = createSchema; function mergeSchemas(schemas, options) { const mergedSchema = combineSchemas(schemas, options); return mergedSchema; } exports.mergeSchemas = mergeSchemas; function extendSchema(schema, value, options) { const valueSchema = createSchema(value, options); const mergedSchema = combineSchemas([schema, valueSchema], options); return mergedSchema; } exports.extendSchema = extendSchema; function createCompoundSchema(values, options) { const schemas = values.map((value) => createSchema(value, options)); return mergeSchemas(schemas, options); } exports.createCompoundSchema = createCompoundSchema; //# sourceMappingURL=schema-builder.js.map