import React, { useState, useEffect, useRef } from 'react'; import { FileJson, Copy, Trash2, Minimize2, Maximize2, CheckCircle, AlertTriangle, Upload, Download, Code, AlignLeft, Wrench } from 'lucide-react'; export default function App() { const [input, setInput] = useState(''); const [output, setOutput] = useState(''); const [error, setError] = useState(null); const [indentSize, setIndentSize] = useState(2); const [stats, setStats] = useState({ size: '0 B', items: 0 }); const [notification, setNotification] = useState(null); const fileInputRef = useRef(null); // Sample data for initial load const sampleData = { "project": "Super JSON Tool", "active": true, "version": 1.0, "features": [ "Syntax Highlighting", "Error Detection", "Minification" ], "metrics": { "rating": 4.8, "users": null } }; useEffect(() => { // Initial load setInput(JSON.stringify(sampleData, null, indentSize)); }, []); useEffect(() => { processJson(input); }, [input, indentSize]); useEffect(() => { if (notification) { const timer = setTimeout(() => setNotification(null), 3000); return () => clearTimeout(timer); } }, [notification]); const showNotification = (message, type = 'success') => { setNotification({ message, type }); }; // Syntax highlighting logic const highlightJson = (jsonStr) => { if (!jsonStr) return ''; // HTML entity escaping to prevent XSS in the preview const escapeHtml = (unsafe) => { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; let processed = jsonStr.replace(/&/g, '&').replace(//g, '>'); // Regex to capture JSON parts return processed.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { let cls = 'text-amber-600'; // number default if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'text-blue-600 font-semibold'; // key } else { cls = 'text-emerald-600'; // string } } else if (/true|false/.test(match)) { cls = 'text-purple-600 font-bold'; // boolean } else if (/null/.test(match)) { cls = 'text-red-500 font-bold'; // null } return '' + match + ''; }); }; const calculateSize = (str) => { const bytes = new Blob([str]).size; if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const processJson = (rawInput) => { if (!rawInput.trim()) { setOutput(''); setError(null); setStats({ size: '0 B', items: 0 }); return; } try { const parsed = JSON.parse(rawInput); const formatted = JSON.stringify(parsed, null, indentSize); setOutput(highlightJson(formatted)); setError(null); // Calculate stats const itemCount = Array.isArray(parsed) ? parsed.length : Object.keys(parsed).length; setStats({ size: calculateSize(rawInput), items: itemCount }); } catch (err) { setError(err.message); // Even if error, we show raw text but without fancy highlighting logic implies valid structure // We'll just plain-text escape it for the output so it doesn't break setOutput(rawInput.replace(/&/g, '&').replace(//g, '>')); } }; // Actions const handleFormat = () => { try { const parsed = JSON.parse(input); setInput(JSON.stringify(parsed, null, indentSize)); showNotification('Formatted successfully'); } catch (e) { showNotification('Cannot format invalid JSON', 'error'); } }; const handleMinify = () => { try { const parsed = JSON.parse(input); setInput(JSON.stringify(parsed)); showNotification('Minified successfully'); } catch (e) { showNotification('Cannot minify invalid JSON', 'error'); } }; const handleClear = () => { setInput(''); showNotification('Cleared editor'); }; const handleCopy = () => { const contentToCopy = error ? input : JSON.stringify(JSON.parse(input), null, indentSize); navigator.clipboard.writeText(contentToCopy).then(() => { showNotification('Copied to clipboard'); }); }; const handleFix = () => { // Basic regex fixes for common lazy-JSON issues let fixed = input .replace(/'/g, '"') // Replace single quotes with double .replace(/,\s*([\]}])/g, '$1') // Remove trailing commas .replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":'); // Quote unquoted keys setInput(fixed); showNotification('Attempted auto-fix'); }; const handleFileUpload = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => setInput(e.target.result); reader.readAsText(file); } }; const handleDownload = () => { if (error) { showNotification('Fix errors before downloading', 'error'); return; } const blob = new Blob([input], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'data.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; return (
Validate, Format & Minify
Input
Waiting for input...
{error}