UNPKG

data-to-jsonschema-to-ts

Version:

Convert plain javascript objects to JSON Schema and TypeScript typings

162 lines 6.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateJsonSchemaFromData = generateJsonSchemaFromData; const lodash_es_1 = require("lodash-es"); const json_schema_types_js_1 = require("./json-schema-types.js"); function generateJsonSchemaFromData(data, title = "MySchema", opts = {}) { const initialSchema = { title, $schema: "http://json-schema.org/draft-07/schema#", }; const allObjectTypes = data.map((o) => getObjectType(o, opts)); if (opts.existingSchema) { allObjectTypes.push({ additionalProperties: opts.existingSchema.additionalProperties, properties: opts.existingSchema.properties, required: opts.existingSchema.required, type: "object", }); } const mergedTopLevel = createOneObjectTypeFromManyObjectTypes(allObjectTypes, opts); return { ...initialSchema, ...mergedTopLevel, }; } function isTypePrimitive(type) { return ["null", "boolean", "number", "string"].includes(type.type); } function getUniqueArrayItems(arr) { const arrItems = new Map(); arr.forEach((i) => { const iTypeStringified = JSON.stringify(i).split("").sort().join(""); if (!arrItems.has(iTypeStringified)) { arrItems.set(iTypeStringified, i); } }); return Array.from(arrItems.values()); } function createOneArrayTypeFromManyArrayTypes(arrayTypes, opts) { if (!arrayTypes.length) return undefined; const newArrayType = { type: "array", }; const allArraysItemTypes = arrayTypes .map((arr) => { if (arr.items && "anyOf" in arr.items && arr.items.anyOf) { return arr.items.anyOf; } }) .filter((t) => t !== undefined) .flat(); const anyOf = []; const objectType = createOneObjectTypeFromManyObjectTypes(allArraysItemTypes.filter((t) => t.type === "object"), opts); const arrType = createOneArrayTypeFromManyArrayTypes(allArraysItemTypes.filter((t) => t.type === "array"), opts); const primitiveTypes = getUniqueArrayItems(allArraysItemTypes.filter((t) => isTypePrimitive(t))); const allTypesForKey = [objectType, arrType, ...primitiveTypes].filter((t) => t !== undefined); anyOf.push(...allTypesForKey); if (anyOf.length) { newArrayType.items = { anyOf }; } return newArrayType; } function createOneObjectTypeFromManyObjectTypes(objectTypes, opts) { if (!objectTypes.length) return undefined; const newObjectType = { type: "object", properties: {}, additionalProperties: opts?.additionalProperties ?? false, }; const keyAndTypes = new Map(); (0, lodash_es_1.flatMap)(objectTypes.map((ot) => Object.entries(ot.properties ?? {}))).forEach(([key, type]) => { const typeList = "oneOf" in type ? type.oneOf : [type]; const existingTypeList = keyAndTypes.get(key) ?? []; keyAndTypes.set(key, getUniqueArrayItems([...typeList, ...existingTypeList])); }); Array.from(keyAndTypes.entries()).forEach(([key, types]) => { const objectTypes = types.filter((t) => t.type === "object"); const objectType = objectTypes.length ? createOneObjectTypeFromManyObjectTypes(objectTypes, opts) : undefined; const arrTypes = types.filter((t) => t.type === "array"); const arrType = createOneArrayTypeFromManyArrayTypes(arrTypes, opts); const primitiveTypes = getUniqueArrayItems(types.filter((t) => isTypePrimitive(t))); const allTypesForKey = [objectType, arrType, ...primitiveTypes].filter((t) => t !== undefined); newObjectType.properties ??= {}; if (allTypesForKey.length > 1) { // @ts-ignore newObjectType.properties[key] = { oneOf: allTypesForKey, }; } else if (allTypesForKey.length === 1) { // @ts-ignore newObjectType.properties[key] = allTypesForKey[0]; } else { delete newObjectType.properties; } }); // create required array const sharedKeys = (0, lodash_es_1.intersection)(...objectTypes.map((ot) => ot.required ?? [])); if (sharedKeys.length) { newObjectType.required = sharedKeys; } return newObjectType; } function getArrayItems(arr, opts) { if (arr.length === 0) { return undefined; } const arrTypes = new Map(); arr.forEach((i) => { const iType = getType(i, opts); const iTypeStringified = JSON.stringify(iType).split("").sort().join(""); if (!arrTypes.has(iTypeStringified)) { arrTypes.set(iTypeStringified, iType); } }); return { anyOf: Array.from(arrTypes.values()) }; } function getType(value, opts) { if (value === null || value === undefined) return { type: "null" }; if (Array.isArray(value)) { return { type: "array", items: getArrayItems(value, opts), }; } if (typeof value === "object") { return getObjectType(value, opts); } return { type: typeof value }; } function getObjectType(obj, opts) { if (!obj) return { type: "object", additionalProperties: json_schema_types_js_1.ANY_TYPE }; const objType = Object.entries(obj).reduce((acc, [key, value]) => { acc.properties ??= {}; // @ts-ignore acc.properties[key] = getType(value); return acc; }, { type: "object", properties: {}, additionalProperties: opts?.additionalProperties ?? false, required: Object.keys(obj), }); if (objType.properties && !Object.keys(objType.properties).length) { objType.additionalProperties = json_schema_types_js_1.ANY_TYPE; } if ("required" in objType && !objType.required?.length) { return { type: "object", properties: objType.properties, }; } return objType; } //# sourceMappingURL=json-schema.js.map