UNPKG

@junaidatari/json2ts

Version:

Convert JSON objects to TypeScript interfaces automatically.

275 lines 11.6 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")); /** * Converts JSON data into TypeScript interface definitions with advanced type inference. * * This class provides comprehensive functionality to analyze JSON objects and generate * corresponding TypeScript interface definitions with intelligent type detection. * It supports nested objects, arrays, primitive types, and handles circular references * to prevent infinite recursion. * * Key Features: * - Intelligent interface naming based on object keys and structure * - Full support for nested objects and arrays * - Circular reference detection to prevent infinite loops * - Flexible export modes (root, all, none) for different use cases * - Automatic type inference for JSON primitives * - Advanced array type detection with tuple types for mixed arrays * - Configurable array tuple size limits for optimal type representation * - Strict type checking mode for more precise type inference * - Custom type mapping for overriding default type detection * * @example * ```typescript * const json = { * name: "John", * age: 30, * isActive: true, * address: { * street: "123 Main St", * city: "New York", * coordinates: [40.7128, -74.0060] * }, * tags: ["user", "active", 2025] * }; * const tsInterface = JsonToTsConverter.convert(json, "User", "root", { * arrayMaxTupleSize: 5, * arrayMinTupleSize: 2, * strict: true, * typeMap: { * 'timestamp': 'Date', * 'uuid': 'string' * } * }); * console.log(tsInterface); * ``` */ class JsonToTsConverter extends ConverterBase_1.default { options; /** * Cache storing generated TypeScript interface definitions. * * Maps interface names to their complete definition strings. This prevents duplicate * interface generation and maintains proper dependency ordering when assembling * the final output. */ interfaces = new Map(); /** * Tracks visited objects to detect circular references. * * Uses WeakSet to avoid memory leaks while preventing infinite recursion * when objects reference themselves or create circular dependencies. */ visitedObjects = new WeakSet(); /** * Creates an instance of JsonToTsConverter. * @param options Configuration options for the conversion process. */ constructor(options = {}) { super(); this.options = options; } /** * Converts JSON data into TypeScript interface strings. * * Static entry point that parses input JSON and generates corresponding * TypeScript interfaces. Supports both object and string inputs. * * @param jsonData - JSON object or string to convert * @param interfaceName - Name for the root interface (default: 'RootObject') * @param exportType - Export mode: 'root', 'all', or 'none' (default: 'root') * @param options - Configuration options for the conversion process * @returns Generated TypeScript interface string or null if parsing fails * * @example * ```typescript * const result = JsonToTsConverter.convert( * '{"name": "John", "age": 30}', * 'Person', * 'all', * { arrayMaxTupleSize: 5, arrayMinTupleSize: 2, strict: true } * ); * ``` */ 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 JsonToTsConverter(options); } /** * Core conversion method that processes parsed JSON data. * * Handles the main conversion logic, including initialization, * interface generation, and final assembly of the output string. * * @param jsonData - Parsed JSON object to convert * @param rootInterfaceName - Name for the root interface * @param exportType - Export mode configuration * @returns Complete TypeScript interface definitions */ convertJson(jsonData, rootInterfaceName, exportType = 'root') { const exports = exportType !== 'none' ? 'export ' : ''; const interfaceName = ConverterUtils_1.default.toInterfaceName(rootInterfaceName); if (typeof jsonData !== 'object' || jsonData === null) { if (this.options.strict) { return `${exports}type ${interfaceName} = null;`; } return `${exports}interface ${interfaceName} {\n [p: string]: unknown;\n}`; } this.interfaces.clear(); this.visitedObjects = new WeakSet(); this.generateInterface(jsonData, interfaceName, exportType === 'all'); // Reverse the order to ensure dependencies are declared before dependents const orderedInterfaces = Array.from(this.interfaces.entries()).reverse(); return orderedInterfaces.map(([x, content]) => { return (x === interfaceName && exportType === 'root' ? exports : '') + content; }).join('\n\n'); } /** * Recursively generates interface definition for an object. * * Analyzes object structure and creates TypeScript interface matching its properties. * Handles nested objects recursively and tracks visited objects to prevent * infinite recursion with circular references. * * @param obj - Object to convert to interface * @param interfaceName - Name for the generated interface * @param appendExport - Whether to include export keyword */ generateInterface(obj, interfaceName, appendExport) { // Prevent infinite recursion from circular references if (typeof obj === 'object' && obj !== null) { if (this.visitedObjects.has(obj)) { return; } this.visitedObjects.add(obj); } if (this.interfaces.has(interfaceName)) return; // Interface already generated let interfaceBody = ''; const keys = Object.keys(obj); for (const key of keys) { const value = obj[key]; const type = this.getType(value, this.capitalize(key), appendExport); interfaceBody += ` ${ConverterUtils_1.default.formatPropertyValue(key, type, this.options)};\n`; } const fullInterface = `${appendExport ? 'export ' : ''}interface ${interfaceName} {\n${interfaceBody.trimEnd()}\n}`; this.interfaces.set(interfaceName, fullInterface); } /** * Determines TypeScript type string for a given value with comprehensive type inference. * * Analyzes value and returns appropriate TypeScript type with support for all JavaScript types * including primitives, complex objects, built-in classes, and special types like Date, RegExp, * Error, Promise, Generator, Function, and collections (Set, Map, WeakMap, WeakSet). Also handles * typed arrays, ArrayBuffer, DataView, Symbols, BigInt, and class instances. * * @param value - Value to analyze * @param parentKey - Property key used for naming child interfaces * @param appendExport - Whether child interfaces should be exported * @returns TypeScript type string representation */ getType(value, parentKey, appendExport) { // Check custom type mapping first if (this.options.typeMap && typeof value !== 'object' && value !== null) { // Check for value-based type mapping (e.g., specific values) if (this.options.typeMap[value]) { return this.options.typeMap[value]; } // Check for type-based mapping (e.g., 'string', 'number') const typeBasedMapping = this.options.typeMap[typeof value]; if (typeBasedMapping) { return typeBasedMapping; } } // Set if (value instanceof Set) { const valueType = value.size > 0 ? this.getType(Array.from(value.values())[0], 'SetValue', false) : 'unknown'; return `Set<${valueType}>`; } // Map if (value instanceof Map) { const entries = Array.from(value.entries()); if (entries.length > 0) { const keyType = this.getType(entries[0][0], 'MapKey', false); const valueType = this.getType(entries[0][1], 'MapValue', false); return `Map<${keyType}, ${valueType}>`; } return 'Map<unknown, unknown>'; } // Handle arrays if (Array.isArray(value) && value.length) { // Use ArrayUtil to detect the appropriate array type return ConverterUtils_1.default.detectTypeFromArray(value, this.options.arrayMaxTupleSize ?? 10, this.options.arrayMinTupleSize ?? 2); } const basicType = ConverterUtils_1.default.detectJsTypeFromObject(value, this.options.strict); if (basicType !== null) return basicType; // Handle built-in objects and special types if (typeof value === 'object' && value !== null) { // Class instance if (value?.constructor?.name !== 'Object') { // Generate interface for class instance properties const interfaceName = this.capitalize(`${parentKey}Instance`); this.generateInterface(value, interfaceName, appendExport); return interfaceName; } // Plain object const interfaceName = this.capitalize(parentKey); this.generateInterface(value, interfaceName, appendExport); return interfaceName; } // Handle strict mode for primitive types if (this.options.strict) { switch (typeof value) { case 'string': return 'string'; case 'number': return Number.isInteger(value) ? 'number' : 'number'; case 'boolean': return 'boolean'; case 'bigint': return 'bigint'; case 'symbol': return 'symbol'; default: return 'unknown'; } } return typeof value; // 'string', 'number', 'boolean', 'bigint', 'symbol', 'function', 'object' } /** * Sanitizes and capitalizes string for interface naming. * * Converts JSON keys into valid TypeScript interface names by removing * invalid characters and capitalizing the first letter. * * @param str - String to process * @returns Sanitized string suitable for interface names */ capitalize(str) { if (!str) return ''; // Remove invalid characters and capitalize first letter const sanitizedKey = str.replace(/[^a-zA-Z0-9_$]/g, ''); return sanitizedKey.charAt(0).toUpperCase() + sanitizedKey.slice(1); } } exports.default = JsonToTsConverter; //# sourceMappingURL=JsonToTsConverter.js.map