/** * Type discriminator for serialization */ enum ValueType { STRING = 'string', NUMBER = 'number', BOOLEAN = 'boolean', OBJECT = 'object', ARRAY = 'array', DATE = 'date', UNDEFINED = 'undefined', NULL = 'null', REGEX = 'regex', BIGINT = 'bigint', SET = 'set', MAP = 'map', ERROR = 'error', } /** * Wrapper for serialized values with type information */ interface SerializedValue { type: ValueType; value: any; } /** * Serialize a value to a string * @param value Value to serialize * @returns Serialized value */ export function serialize(value: T): string { const wrapped = wrapWithType(value); return JSON.stringify(wrapped); } /** * Deserialize a value from a string * @param serialized Serialized value * @returns Deserialized value */ export function deserialize(serialized: string): T { try { const wrapped = JSON.parse(serialized) as SerializedValue; return unwrapWithType(wrapped) as T; } catch (error) { // If not a structured serialized value, try direct parsing return JSON.parse(serialized); } } /** * Wrap a value with type information * @param value Value to wrap * @returns Wrapped value with type information */ function wrapWithType(value: any): SerializedValue { if (value === undefined) { return { type: ValueType.UNDEFINED, value: null }; } if (value === null) { return { type: ValueType.NULL, value: null }; } // Handle different types switch (typeof value) { case 'string': return { type: ValueType.STRING, value }; case 'number': return { type: ValueType.NUMBER, value }; case 'boolean': return { type: ValueType.BOOLEAN, value }; case 'bigint': return { type: ValueType.BIGINT, value: value.toString() }; case 'object': // Handle special objects if (value instanceof Date) { return { type: ValueType.DATE, value: value.toISOString() }; } if (value instanceof RegExp) { return { type: ValueType.REGEX, value: { pattern: value.source, flags: value.flags } }; } if (value instanceof Set) { return { type: ValueType.SET, value: Array.from(value).map((item: any) => wrapWithType(item)) }; } if (value instanceof Map) { return { type: ValueType.MAP, value: Array.from(value.entries()).map(([k, v]) => ({ key: wrapWithType(k), value: wrapWithType(v) })) }; } if (value instanceof Error) { return { type: ValueType.ERROR, value: { name: value.name, message: value.message, stack: value.stack } }; } if (Array.isArray(value)) { return { type: ValueType.ARRAY, value: value.map((item: any) => wrapWithType(item)) }; } // Regular object const wrappedObj: Record = {}; for (const [key, val] of Object.entries(value)) { wrappedObj[key] = wrapWithType(val); } return { type: ValueType.OBJECT, value: wrappedObj }; default: // Convert to string as fallback return { type: ValueType.STRING, value: String(value) }; } } /** * Unwrap a value with type information * @param wrapped Wrapped value with type information * @returns Unwrapped value */ function unwrapWithType(wrapped: SerializedValue): any { switch (wrapped.type) { case ValueType.UNDEFINED: return undefined; case ValueType.NULL: return null; case ValueType.STRING: case ValueType.NUMBER: case ValueType.BOOLEAN: return wrapped.value; case ValueType.BIGINT: return BigInt(wrapped.value); case ValueType.DATE: return new Date(wrapped.value); case ValueType.REGEX: return new RegExp(wrapped.value.pattern, wrapped.value.flags); case ValueType.SET: return new Set(wrapped.value.map((item: SerializedValue) => unwrapWithType(item))); case ValueType.MAP: return new Map( wrapped.value.map(({ key, value }: { key: SerializedValue, value: SerializedValue }) => [ unwrapWithType(key), unwrapWithType(value) ]) ); case ValueType.ERROR: const error = new Error(wrapped.value.message); error.name = wrapped.value.name; error.stack = wrapped.value.stack; return error; case ValueType.ARRAY: return wrapped.value.map((item: SerializedValue) => unwrapWithType(item)); case ValueType.OBJECT: const unwrappedObj: Record = {}; for (const [key, val] of Object.entries(wrapped.value)) { unwrappedObj[key] = unwrapWithType(val as SerializedValue); } return unwrappedObj; default: // Default fallback return wrapped.value; } } /** * Safe stringification that handles circular references * @param value Value to stringify * @returns JSON string */ export function safeStringify(value: any): string { const seen = new WeakSet(); return JSON.stringify(value, (key, val) => { if (typeof val === 'object' && val !== null) { if (seen.has(val)) { return '[Circular Reference]'; } seen.add(val); } return val; }); }