UNPKG

@junaidatari/json2ts

Version:

Convert JSON objects to TypeScript interfaces automatically.

220 lines 8.87 kB
"use strict"; /** * @author Junaid Atari <mj.atari@gmail.com> * @copyright 2025 Junaid Atari * @see https://github.com/blacksmoke26 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // base const ConverterBase_1 = __importDefault(require("../base/ConverterBase")); // utils const ConverterUtils_1 = __importDefault(require("../utils/ConverterUtils")); /** * A utility class that converts JSON objects into flattened TypeScript interfaces. * This converter embeds all nested object properties into a single interface definition, * eliminating the need for separate interface definitions for nested structures. * * Key features: * - Converts JSON objects to TypeScript interfaces * - Embeds nested objects into the main interface * - Handles arrays and primitive types appropriately * - Prevents infinite recursion with circular reference detection * - Supports custom interface names and export types * * @example * Input JSON: * ```json * { * "user": { * "id": 1, * "name": "John Doe" * }, * "posts": [ * { "title": "First Post", "content": "Hello World" } * ] * } * ``` * * Generated TypeScript interface: * ```typescript * export interface RootObject { * user: { * id: number; * name: string; * }; * posts: { * title: string; * content: string; * }[]; * } * ``` */ class JsonToFlattenedTsConverter extends ConverterBase_1.default { options; /** * Tracks visited objects during conversion to prevent infinite recursion * when circular references are encountered in the input JSON. * Uses WeakSet to avoid memory leaks for objects that are no longer referenced. */ visitedObjects = new WeakSet(); /** * Creates an instance of JsonToFlattenedTsConverter. * @param options Configuration options for the conversion process. */ constructor(options = {}) { super(); this.options = options; } /** * Converts a JSON object or string into a flattened TypeScript interface. * * @param jsonData - The JSON data to convert. Can be an object or a JSON string. * @param interfaceName - Name for the generated interface. Defaults to 'RootObject'. * @param exportType - Type of export ('root' or 'interface'). Defaults to 'root'. * @param options Configuration options for the conversion process, including array * tuple size constraints and other conversion settings. * @returns The generated TypeScript interface as a string, or null if input is invalid. * * @example * ```typescript * const jsonData = { * user: { id: 1, name: "John" }, * posts: [{ title: "Hello", content: "World" }] * }; * const interfaceString = JsonToFlattenedTsConverter.convert(jsonData, "UserData"); * console.log(interfaceString); * // Output: * // export interface UserData { * // user: { * // id: number; * // name: string; * // }; * // posts: { * // title: string; * // content: string; * // }[]; * // } * ``` */ static convert(jsonData, interfaceName = 'RootObject', exportType = 'root', options = {}) { return super.convert(jsonData, interfaceName, exportType, options); } /** * Factory method to create converter instance. */ static createConverter(options) { return new JsonToFlattenedTsConverter(options); } /** * Generates the TypeScript interface code from parsed JSON data. * * @param jsonData - The parsed JSON object to convert. * @param interfaceName - Name for the generated interface. * @param exportType - Type of export ('root' or 'interface'). * @returns The complete TypeScript interface code. */ convertJson(jsonData, interfaceName, exportType = 'root') { const exports = exportType !== 'none' ? 'export ' : ''; const safeInterfaceName = ConverterUtils_1.default.toInterfaceName(interfaceName); if (typeof jsonData !== 'object' || jsonData === null) { return exports + `interface ${safeInterfaceName} {}`; } // Reset the visited set for each conversion run this.visitedObjects = new WeakSet(); const interfaceBody = this.generateObjectBody(jsonData, 0).trim(); return exports + `interface ${safeInterfaceName} ${interfaceBody}`.replace(/\[]$/, ''); } /** * Recursively generates TypeScript type definitions for objects and their properties. * Embeds nested objects into the current interface definition. * * @param obj - The object or value to convert to a TypeScript type. * @param indentLevel - Current indentation level for formatting the output. * @returns A string containing the TypeScript type definition. */ generateObjectBody(obj, indentLevel) { const indent = this.getIndent(indentLevel); const nextIndent = this.getIndent(indentLevel + 1); // Handle arrays if (Array.isArray(obj) && obj.length) { // Check if array contains only primitives or mixed types const containsOnlyPrimitives = obj.every(item => item === null || typeof item !== 'object'); if (containsOnlyPrimitives) { // Use ArrayUtil to detect the array type (including tuple detection) const maxTupleSize = this.options.arrayMaxTupleSize ?? 10; const minTupleSize = this.options.arrayMinTupleSize ?? 2; return ConverterUtils_1.default.detectTypeFromArray(obj, maxTupleSize, minTupleSize); } // For arrays with objects, use the first element's type as the representative const elementType = this.getType(obj[0], indentLevel); return `${elementType}[]`; } const basicType = ConverterUtils_1.default.detectJsTypeFromObject(obj); if (basicType !== null) return basicType; // --- Handle Objects --- // Check for circular references if (this.visitedObjects.has(obj)) { return this.options.strict ? 'unknown' : 'any'; // Fallback for circular references } // Mark object as visited to detect cycles this.visitedObjects.add(obj); let body = ''; const keys = Object.keys(obj); // Include symbol properties if they exist const symbolKeys = Object.getOwnPropertySymbols(obj); if (symbolKeys.length > 0) { // Add symbol properties with their key.toString() for (const symKey of symbolKeys) { const value = obj[symKey]; const type = this.getType(value, indentLevel + 1); const symbolName = symKey.toString().replace('Symbol(', '').replace(')', ''); body += `${nextIndent}[${symbolName}]: ${type};\n`; } } for (const key of keys) { const value = obj[key]; const type = this.getType(value, indentLevel + 1); body += `${nextIndent}${ConverterUtils_1.default.formatPropertyValue(key, type, this.options)};\n`; } // Remove object from visited set after processing this.visitedObjects.delete(obj); // Handle empty objects if (keys.length === 0 && symbolKeys.length === 0) { return '{}'; } // Return the embedded object definition return `{\n${body.trimEnd()}\n${indent}}`; } /** * Determines the appropriate TypeScript type for a given value. * Delegates to generateObjectBody for complex types (objects and arrays). * * @param value - The value to analyze for type determination. * @param indentLevel - Current indentation level for formatting. * @returns The TypeScript type string for the given value. */ getType(value, indentLevel) { if (value === null || typeof value !== 'object') { // Direct handling for primitives and null return this.generateObjectBody(value, indentLevel); } // Delegate to main recursive logic for objects and arrays return this.generateObjectBody(value, indentLevel); } /** * Creates an indentation string based on the specified level. * Uses two spaces per indentation level for consistent formatting. * * @param level - The number of indentation levels. * @returns A string containing the appropriate number of spaces. */ getIndent(level) { return ' '.repeat(level); } } exports.default = JsonToFlattenedTsConverter; //# sourceMappingURL=JsonToFlattenedTsConverter.js.map