import { useState, memo } from "react"; import { JsonValue } from "./DynamicJsonForm"; import clsx from "clsx"; interface JsonViewProps { data: unknown; name?: string; initialExpandDepth?: number; } function tryParseJson(str: string): { success: boolean; data: JsonValue } { const trimmed = str.trim(); if ( !(trimmed.startsWith("{") && trimmed.endsWith("}")) && !(trimmed.startsWith("[") && trimmed.endsWith("]")) ) { return { success: false, data: str }; } try { return { success: true, data: JSON.parse(str) }; } catch { return { success: false, data: str }; } } const JsonView = memo( ({ data, name, initialExpandDepth = 3 }: JsonViewProps) => { const normalizedData = typeof data === "string" ? tryParseJson(data).success ? tryParseJson(data).data : data : data; return (
); }, ); JsonView.displayName = "JsonView"; interface JsonNodeProps { data: JsonValue; name?: string; depth: number; initialExpandDepth: number; } const JsonNode = memo( ({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => { const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth); const getDataType = (value: JsonValue): string => { if (Array.isArray(value)) return "array"; if (value === null) return "null"; return typeof value; }; const dataType = getDataType(data); const typeStyleMap: Record = { number: "text-blue-600", boolean: "text-amber-600", null: "text-purple-600", undefined: "text-gray-600", string: "text-green-600 break-all whitespace-pre-wrap", default: "text-gray-700", }; const renderCollapsible = (isArray: boolean) => { const items = isArray ? (data as JsonValue[]) : Object.entries(data as Record); const itemCount = items.length; const isEmpty = itemCount === 0; const symbolMap = { open: isArray ? "[" : "{", close: isArray ? "]" : "}", collapsed: isArray ? "[ ... ]" : "{ ... }", empty: isArray ? "[]" : "{}", }; if (isEmpty) { return (
{name && ( {name}: )} {symbolMap.empty}
); } return (
setIsExpanded(!isExpanded)} > {name && ( {name}: )} {isExpanded ? ( {symbolMap.open} ) : ( <> {symbolMap.collapsed} {itemCount} {itemCount === 1 ? "item" : "items"} )}
{isExpanded && ( <>
{isArray ? (items as JsonValue[]).map((item, index) => (
)) : (items as [string, JsonValue][]).map(([key, value]) => (
))}
{symbolMap.close}
)}
); }; const renderString = (value: string) => { const maxLength = 100; const isTooLong = value.length > maxLength; if (!isTooLong) { return (
{name && ( {name}: )}
"{value}"
); } return (
{name && ( {name}: )}
 setIsExpanded(!isExpanded)}
            title={isExpanded ? "Click to collapse" : "Click to expand"}
          >
            {isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`}
          
); }; switch (dataType) { case "object": case "array": return renderCollapsible(dataType === "array"); case "string": return renderString(data as string); default: return (
{name && ( {name}: )} {data === null ? "null" : String(data)}
); } }, ); JsonNode.displayName = "JsonNode"; export default JsonView;