UNPKG

json-to-schema-generator

Version:

json-to-schema is a lightweight and powerful utility that generates JSON Schema from a given JSON object. It automatically detects the structure of the JSON, including nested objects and arrays, and generates a corresponding schema that can be used for va

129 lines (107 loc) 4.23 kB
import Ajv from "ajv"; type JsonObject = { [key: string]: any }; interface JsonSchema { type: string; properties?: { [key: string]: any }; items?: { type: string; properties?: { [key: string]: any }; }; required?: string[]; // Marking as optional since it can be undefined initially } const generateJsonSchema = (jsonObject: any): JsonSchema => { const schema: JsonSchema = { type: Array.isArray(jsonObject) ? "array" : "object", properties: {}, required: [] // Initialize required as an empty array }; const getType = (value: any): string => { if (Array.isArray(value)) { return "array"; } else if (value && typeof value === "object") { // Handle special object types (Date, RegExp, etc.) if (value instanceof Date) { return "string"; // Dates are represented as strings in JSON } else if (value instanceof RegExp) { return "string"; // Regular expressions are stored as strings } return "object"; // Generic object } else { return typeof value; // Primitive types (string, number, etc.) } }; const processObject = (obj: JsonObject, required: string[] = []): { [key: string]: any } => { const properties: { [key: string]: any } = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; const type = getType(value); properties[key] = { type }; if (type === "object") { // Recursively process nested objects and collect required fields for this level properties[key].properties = processObject(value, []); // If a nested object has required fields, add them to its own `required` field if (properties[key].properties && Object.keys(properties[key].properties).length > 0) { properties[key].required = Object.keys(properties[key].properties); } } else if (type === "array") { if (value.length > 0) { // Check the type of the first element const firstElementType = getType(value[0]); if (firstElementType === "object") { // Handle arrays of objects (nested objects) properties[key].items = { type: "object", properties: processObject(value[0], []) }; } else { // Handle arrays of primitive types (string, number, etc.) properties[key].items = { type: firstElementType }; } } else { // Empty arrays - use "any" type for items properties[key].items = { type: "any" }; } } // Mark required fields only if they exist (don't mark null or undefined values) if (value !== null && value !== undefined) { required.push(key); } } } return properties; }; if (Array.isArray(jsonObject)) { // If the root object is an array, handle array-specific logic schema.items = { type: getType(jsonObject[0]) }; // Assuming all items are of the same type if (jsonObject.length > 0 && typeof jsonObject[0] === "object") { schema.items = { type: "object", properties: processObject(jsonObject[0], []) }; } else if (jsonObject.length > 0) { schema.items = { type: typeof jsonObject[0] }; } else { schema.items = { type: "any" }; // Empty array, assign type "any" } } else { // Process object case schema.properties = processObject(jsonObject, schema.required); } // Remove duplicates in the required array (if any) schema.required = [...new Set(schema.required)]; // If the root is an array, don't need properties and required if (Array.isArray(jsonObject)) { delete schema.properties; delete schema.required; } return schema; }; //Validate JSON against a schema using Ajv const validateJsonWithSchema = (jsonObject: any, schema: any) => { const ajv = new Ajv(); const validate = ajv.compile(schema); const isValid = validate(jsonObject); if (!isValid) { return { isValid: false, errors: validate.errors, }; } return { isValid: true, errors: null, }; }; export { generateJsonSchema, validateJsonWithSchema };