import { useState } from 'react'; import { useLanguage } from '../../../contexts/LanguageContext'; import styles from '../FilePreview.module.css'; interface JsonRendererProps { previewContent: string; fileName: string; } export function JsonRenderer({ previewContent, fileName }: JsonRendererProps) { const { t } = useLanguage(); const [collapsedRows, setCollapsedRows] = useState>(new Set()); const handleCopyJson = () => { try { const parsedJson = JSON.parse(previewContent); const formattedJson = JSON.stringify(parsedJson, null, 2); navigator.clipboard.writeText(formattedJson); } catch (error) { navigator.clipboard.writeText(previewContent); } }; const formatTimestamp = (value: string | number): string => { const numValue = typeof value === 'string' ? parseFloat(value) : value; if (!isNaN(numValue) && numValue > 1000000000 && numValue < 4102444800) { const date = new Date(numValue * 1000); if (!isNaN(date.getTime())) { return date.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } } return String(value); }; const preprocessJson = (obj: any, parentKey = ''): {keys: string[], values: any[], types: (string | 'timestamp')[], isNested: boolean[]} => { const keys: string[] = []; const values: any[] = []; const types: (string | 'timestamp')[] = []; const isNested: boolean[] = []; if (obj === null || obj === undefined) { keys.push(parentKey); values.push(String(obj)); types.push(typeof obj); isNested.push(false); } else if (typeof obj === 'boolean' || typeof obj === 'number') { keys.push(parentKey); if (typeof obj === 'number' && (parentKey.toLowerCase().includes('timestamp') || parentKey.toLowerCase().includes('time'))) { values.push(formatTimestamp(obj)); types.push('timestamp'); } else { values.push(obj); types.push(typeof obj); } isNested.push(false); } else if (typeof obj === 'string') { keys.push(parentKey); const numValue = parseFloat(obj); if (!isNaN(numValue) && (parentKey.toLowerCase().includes('timestamp') || parentKey.toLowerCase().includes('time'))) { values.push(formatTimestamp(obj)); types.push('timestamp'); } else { try { const parsedString = JSON.parse(obj); if (typeof parsedString === 'object' && parsedString !== null) { // For stringified JSON objects, process them recursively const nestedData = preprocessJson(parsedString, ''); values.push(nestedData); types.push(Array.isArray(parsedString) ? 'array' : 'object'); isNested.push(true); } else { values.push(obj); types.push('string'); isNested.push(false); } } catch (e) { values.push(obj); types.push('string'); isNested.push(false); } } } else if (Array.isArray(obj)) { if (obj.length === 0) { keys.push(parentKey); values.push(''); types.push('array'); isNested.push(false); } else { const allPrimitive = obj.every(item => item === null || typeof item !== 'object' || (typeof item === 'object' && !Array.isArray(item) && Object.keys(item).length === 0) ); if (allPrimitive) { if (parentKey) { keys.push(parentKey); values.push({ isArray: true, items: obj, length: obj.length }); types.push('array'); isNested.push(true); } else { // For root-level arrays, always use compact array display keys.push(''); values.push({ isArray: true, items: obj, length: obj.length }); types.push('array'); isNested.push(true); } } else { const nestedKeys: string[] = []; const nestedValues: any[] = []; const nestedTypes: (string | 'timestamp')[] = []; const nestedIsNested: boolean[] = []; obj.forEach((item, index) => { if (typeof item === 'object' && item !== null) { const nestedData = preprocessJson(item, `Item ${index + 1}`); nestedKeys.push(...nestedData.keys); nestedValues.push(...nestedData.values); nestedTypes.push(...nestedData.types); nestedIsNested.push(...nestedData.isNested); } else { nestedKeys.push(`Item ${index + 1}`); nestedValues.push(String(item)); nestedTypes.push(typeof item); nestedIsNested.push(false); } }); if (parentKey) { keys.push(parentKey); values.push({ keys: nestedKeys, values: nestedValues, types: nestedTypes, isNested: nestedIsNested }); types.push('array'); isNested.push(true); } else { keys.push(...nestedKeys); values.push(...nestedValues); types.push(...nestedTypes); isNested.push(...nestedIsNested); } } } } else if (typeof obj === 'object') { const entries = Object.entries(obj); if (entries.length === 0) { keys.push(parentKey); values.push('{}'); types.push('object'); isNested.push(false); } else { const nestedKeys: string[] = []; const nestedValues: any[] = []; const nestedTypes: (string | 'timestamp')[] = []; const nestedIsNested: boolean[] = []; entries.forEach(([key, value]) => { if (typeof value === 'object' && value !== null) { const nestedData = preprocessJson(value, key); nestedKeys.push(...nestedData.keys); nestedValues.push(...nestedData.values); nestedTypes.push(...nestedData.types); nestedIsNested.push(...nestedData.isNested); } else { let processedValue = value; let processedType: string | 'timestamp' = typeof value; if (typeof value === 'number' && (key.toLowerCase().includes('timestamp') || key.toLowerCase().includes('time'))) { processedValue = formatTimestamp(value); processedType = 'timestamp'; } else if (typeof value === 'string') { const numValue = parseFloat(value); if (!isNaN(numValue) && (key.toLowerCase().includes('timestamp') || key.toLowerCase().includes('time'))) { processedValue = formatTimestamp(value); processedType = 'timestamp'; } } nestedKeys.push(key); nestedValues.push(processedValue); nestedTypes.push(processedType); nestedIsNested.push(false); } }); if (parentKey) { keys.push(parentKey); values.push({ keys: nestedKeys, values: nestedValues, types: nestedTypes, isNested: nestedIsNested }); types.push('object'); isNested.push(true); } else { keys.push(...nestedKeys); values.push(...nestedValues); types.push(...nestedTypes); isNested.push(...nestedIsNested); } } } return { keys, values, types, isNested }; }; const renderTable = (data: {keys: string[], values: any[], types: (string | 'timestamp')[], isNested: boolean[]}, level = 0, parentPath = '') => { if (!data || data.keys.length === 0) return null; const toggleCollapse = (rowPath: string) => { const newCollapsed = new Set(collapsedRows); if (newCollapsed.has(rowPath)) { newCollapsed.delete(rowPath); } else { newCollapsed.add(rowPath); } setCollapsedRows(newCollapsed); }; const isLongContent = (value: any, type: string): boolean => { if (typeof value === 'string') { return value.includes('\n') || value.length > 80; } if (type === 'array' && Array.isArray(value)) { return value.length > 8; } if (typeof value === 'object' && value !== null && 'keys' in value) { return value.keys.length > 1; } if (typeof value === 'object' && value !== null && 'isArray' in value) { return value.length > 8; } return false; }; const getPreview = (value: any, type: string): string => { if (typeof value === 'string') { if (value.includes('\n')) { const firstLine = value.split('\n')[0]; return firstLine.length > 60 ? firstLine.substring(0, 60) + '...' : firstLine + '...'; } if (value.length > 80) { return value.substring(0, 80) + '...'; } return value; } if (type === 'array' && Array.isArray(value)) { return `[${value.slice(0, 3).map(item => typeof item === 'string' ? `"${item}"` : String(item)).join(', ')}${value.length > 3 ? '...' : ''}]`; } if (typeof value === 'object' && value !== null && 'isArray' in value) { return `[${value.items.slice(0, 3).map((item: any) => typeof item === 'string' ? `"${item}"` : String(item)).join(', ')}${value.length > 3 ? '...' : ''}]`; } if (typeof value === 'object' && value !== null && 'keys' in value) { if (type === 'array') { return `[${value.keys.slice(0, 3).join(', ')}${value.keys.length > 3 ? '...' : ''}]`; } else { return `{${value.keys.slice(0, 3).join(', ')}${value.keys.length > 3 ? '...' : ''}}`; } } return String(value); }; return (
0 ? styles.nestedTable : styles.jsonTable} ${level > 0 ? styles.nestedTableIndented : ''}`}>
0 ? styles.nestedTableBody : styles.jsonTableBody}> {data.keys.map((key, index) => { const typeClass = data.types[index] ? `jsonValue${data.types[index].charAt(0).toUpperCase() + data.types[index].slice(1)}` : ''; const typeClassName = styles[typeClass] || ''; const rowPath = `${parentPath}.${key}`; const isCollapsed = collapsedRows.has(rowPath); const shouldShowCollapse = isLongContent(data.values[index], data.types[index]); return (
0 ? styles.nestedTableRow : styles.jsonTableRow} ${isCollapsed ? styles.collapsedRow : styles.notCollapsedRow}`} >
0 ? styles.nestedTableKey : styles.jsonTableKey}> {key} {shouldShowCollapse && ( )}
0 ? styles.nestedTableValue : styles.jsonTableValue} ${typeClassName}`}> {data.isNested[index] ? (
{shouldShowCollapse && isCollapsed ? ( {getPreview(data.values[index], data.types[index])} ) : ( data.values[index].isArray ? (
{isCollapsed ? (
{data.values[index].items.slice(0, 10).map((item: any) => String(item)).join(', ')} {data.values[index].length > 10 && `, ... (${data.values[index].length} items)`}
) : ( <> {data.values[index].items.map((item: any, itemIndex: number) => (
{String(item)}
))} )}
) : ( typeof data.values[index] === 'object' && data.values[index] !== null && 'keys' in data.values[index] ? renderTable(data.values[index], level + 1, rowPath) : Error: Invalid nested data ) )}
) : (
{shouldShowCollapse && isCollapsed ? ( {getPreview(data.values[index], data.types[index])} ) : ( {String(data.values[index])} )}
)}
); })}
); }; try { const parsedJson = JSON.parse(previewContent); let preprocessedData; if (Array.isArray(parsedJson)) { preprocessedData = preprocessJson(parsedJson, 'root'); } else if (typeof parsedJson === 'object' && parsedJson !== null) { if (parsedJson.result && typeof parsedJson.result === 'string') { try { const parsedResult = JSON.parse(parsedJson.result); parsedJson.result = parsedResult; } catch (e) { // If parsing fails, keep as string - it will be handled by the string processing logic } } const entries = Object.entries(parsedJson); preprocessedData = { keys: entries.map(([key]) => key), values: entries.map(([key, value]) => { if (typeof value === 'object' && value !== null) { const processed = preprocessJson(value, ''); return processed; } else if (typeof value === 'string') { try { const parsedString = JSON.parse(value); if (typeof parsedString === 'object' && parsedString !== null) { return preprocessJson(parsedString, ''); } else { let processedValue = value.replace(/\\n/g, ' ').replace(/\n/g, ' '); const numValue = parseFloat(value); if (!isNaN(numValue) && (key.toLowerCase().includes('timestamp') || key.toLowerCase().includes('time'))) { processedValue = formatTimestamp(value); } return String(processedValue); } } catch (e) { let processedValue = value.replace(/\\n/g, ' ').replace(/\n/g, ' '); const numValue = parseFloat(value); if (!isNaN(numValue) && (key.toLowerCase().includes('timestamp') || key.toLowerCase().includes('time'))) { processedValue = formatTimestamp(value); } return String(processedValue); } } else { let processedValue = value; if (typeof value === 'number' && (key.toLowerCase().includes('timestamp') || key.toLowerCase().includes('time'))) { processedValue = formatTimestamp(value); return String(processedValue); } return processedValue; } }), types: entries.map(([, value]) => { if (typeof value === 'object' && value !== null) { return Array.isArray(value) ? 'array' : 'object'; } else if (typeof value === 'string') { try { const parsedString = JSON.parse(value); if (typeof parsedString === 'object' && parsedString !== null) { return Array.isArray(parsedString) ? 'array' : 'object'; } } catch (e) { // Ignore parsing errors } return 'string'; } return typeof value; }), isNested: entries.map(([, value]) => { if (typeof value === 'object' && value !== null) { return true; } else if (typeof value === 'string') { try { const parsedString = JSON.parse(value); return typeof parsedString === 'object' && parsedString !== null; } catch (e) { return false; } } return false; }) }; console.log(preprocessedData); } else { preprocessedData = { keys: ['value'], values: [String(parsedJson)], types: [typeof parsedJson], isNested: [false] }; } return (
{preprocessedData.keys.length} {t('files.preview.json.properties', 'properties')}
{renderTable(preprocessedData, 0, 'root')}
); } catch (parseError) { const rawData = { keys: ['Raw Content'], values: [previewContent], types: ['string'], isNested: [false] }; return (
{t('files.preview.json.invalid', 'Raw Content (Invalid JSON)')}: {fileName}
{renderTable(rawData)}
); } }