// ======================================== // JsonCardView Component // ======================================== // Renders JSON data as structured cards for better readability import { useState } from 'react'; import { ChevronDown, ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Card } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; // ========== Types ========== export interface JsonCardViewProps { /** JSON data to render - accepts any object type */ data: object | unknown[] | null; /** Additional CSS className */ className?: string; /** Initial expanded state */ defaultExpanded?: boolean; } interface CardItemProps { label: string; value: unknown; depth?: number; } // ========== Helper Functions ========== function isObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function isArray(value: unknown): value is unknown[] { return Array.isArray(value); } function formatLabel(key: string): string { return key .replace(/_/g, ' ') .replace(/([A-Z])/g, ' $1') .replace(/^./, (str) => str.toUpperCase()) .trim(); } // ========== Sub Components ========== function PrimitiveValue({ value }: { value: unknown }) { if (value === null || value === undefined) { return null; } if (typeof value === 'boolean') { return ( {value ? 'true' : 'false'} ); } if (typeof value === 'number') { return {value}; } if (typeof value === 'string') { // Check if it's a URL if (value.startsWith('http://') || value.startsWith('https://')) { return ( {value} ); } // Long text if (value.length > 100) { return (
{value}
); } return {value}; } return {String(value)}; } function ArrayView({ items }: { items: unknown[] }) { const [expanded, setExpanded] = useState(true); if (items.length === 0) { return (
Empty list
); } // Simple array of primitives const allPrimitives = items.every( (item) => typeof item !== 'object' || item === null ); if (allPrimitives) { return (
{items.map((item, index) => ( {String(item)} ))}
); } return (
{expanded && (
{items.map((item, index) => (
#{index + 1}
{isObject(item) ? ( ) : ( )}
))}
)}
); } function ObjectView({ data, depth = 0 }: { data: Record; depth?: number }) { const entries = Object.entries(data); if (entries.length === 0) { return
Empty object
; } return (
{entries.map(([key, value]) => ( ))}
); } function CardItem({ label, value, depth = 0 }: CardItemProps) { const formattedLabel = formatLabel(label); // Nested object if (isObject(value)) { return (
{formattedLabel}
1 && 'ml-2')}>
); } // Array if (isArray(value)) { return (
{formattedLabel}
); } // Primitive value return (
{formattedLabel}
); } // ========== Main Component ========== export function JsonCardView({ data, className }: JsonCardViewProps) { if (!data) { return (
No data available
); } // Handle array at root level if (isArray(data)) { return (
); } // Handle object return (
} />
); } export default JsonCardView;