/** * オブジェクトのシリアライゼーション可能性をチェックし、 * 問題のあるプロパティを特定するユーティリティ */ export function debugSerialization(obj: unknown, path = 'root'): { serializable: boolean; issues: string[] } { const issues: string[] = []; function checkValue(value: unknown, currentPath: string): boolean { // null/undefined/プリミティブ型は安全 if (value === null || value === undefined) return true; if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true; // 配列のチェック if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { if (!checkValue(value[i], `${currentPath}[${i}]`)) { return false; } } return true; } // オブジェクトのチェック if (typeof value === 'object') { const constructor = value.constructor; const constructorName = constructor?.name; // 問題のあるコンストラクタを検出 if (constructorName && !['Object', 'Array'].includes(constructorName)) { issues.push(`${currentPath}: Non-plain object with constructor '${constructorName}'`); // 特定の問題のあるクラスを詳細に記録 if (constructorName === 'Request') { issues.push(`${currentPath}: Request object (from fetch API or Gradio Client)`); } else if (constructorName === 'Headers') { issues.push(`${currentPath}: Headers object (from fetch API or Gradio Client)`); } else if (constructorName === 'AbortSignal') { issues.push(`${currentPath}: AbortSignal object (from fetch API or Gradio Client)`); } else if (constructorName === 'Date') { issues.push(`${currentPath}: Date object - use toISOString() or getTime()`); } else if (constructorName === 'Map' || constructorName === 'Set') { issues.push(`${currentPath}: ${constructorName} object - convert to plain object/array`); } return false; } // 関数のチェック if (typeof value === 'function') { issues.push(`${currentPath}: Function detected`); return false; } // Symbol のチェック if (typeof value === 'symbol') { issues.push(`${currentPath}: Symbol detected`); return false; } // オブジェクトのプロパティを再帰的にチェック for (const [key, val] of Object.entries(value)) { if (!checkValue(val, `${currentPath}.${key}`)) { // 既に issues に追加されているので、falseを返すだけ } } return issues.length === 0; } return true; } const serializable = checkValue(obj, path); // 実際にJSON.stringifyを試してエラーを検出 try { JSON.stringify(obj); } catch (error) { issues.push(`JSON.stringify error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return { serializable, issues }; } /** * オブジェクトをコンソールに詳細にダンプする */ export function dumpObjectStructure(obj: unknown, maxDepth = 3, currentDepth = 0): void { const indent = ' '.repeat(currentDepth); if (currentDepth >= maxDepth) { console.log(`${indent}[Max depth reached]`); return; } if (obj === null || obj === undefined) { console.log(`${indent}${obj}`); return; } if (typeof obj !== 'object') { console.log(`${indent}${typeof obj}: ${obj}`); return; } const constructor = obj.constructor; console.log(`${indent}${constructor?.name || 'Object'} {`); for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { console.log(`${indent} ${key}: ${value}`); } else if (typeof value === 'object') { const valueConstructor = value.constructor; console.log(`${indent} ${key}: ${valueConstructor?.name || 'Object'} ${Array.isArray(value) ? `[${value.length}]` : ''}`); if (!['Request', 'Headers', 'AbortSignal', 'Client'].includes(valueConstructor?.name || '')) { dumpObjectStructure(value, maxDepth, currentDepth + 2); } } else if (typeof value === 'function') { console.log(`${indent} ${key}: [Function]`); } else { console.log(`${indent} ${key}: ${typeof value}`); } } console.log(`${indent}}`); }