Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>JSON Formatter & Validator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .json-key { | |
| color: #9CDCFE; | |
| } | |
| .json-value { | |
| color: #CE9178; | |
| } | |
| .json-string { | |
| color: #CE9178; | |
| } | |
| .json-number { | |
| color: #B5CEA8; | |
| } | |
| .json-boolean { | |
| color: #569CD6; | |
| } | |
| .json-null { | |
| color: #569CD6; | |
| } | |
| .json-punctuation { | |
| color: #D4D4D4; | |
| } | |
| .error-line { | |
| background-color: rgba(255, 0, 0, 0.1); | |
| position: relative; | |
| } | |
| .error-line::after { | |
| content: attr(data-error); | |
| position: absolute; | |
| left: 0; | |
| bottom: -20px; | |
| color: #ff6b6b; | |
| font-size: 12px; | |
| font-family: monospace; | |
| } | |
| #jsonInput { | |
| min-height: 200px; | |
| font-family: 'Courier New', Courier, monospace; | |
| } | |
| #formattedJson { | |
| min-height: 200px; | |
| font-family: 'Courier New', Courier, monospace; | |
| white-space: pre-wrap; | |
| } | |
| .copy-btn { | |
| transition: all 0.2s; | |
| } | |
| .copy-btn:hover { | |
| transform: scale(1.1); | |
| } | |
| .copy-btn.copied { | |
| color: #4CAF50; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-purple-400 via-pink-500 to-red-500 bg-clip-text text-transparent"> | |
| JSON Formatter & Validator | |
| </h1> | |
| <p class="text-gray-400">Paste, format, validate and beautify your JSON data</p> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <!-- Input Section --> | |
| <div class="bg-gray-800 rounded-lg shadow-lg overflow-hidden"> | |
| <div class="bg-gray-700 px-4 py-3 flex justify-between items-center"> | |
| <h2 class="font-semibold"> | |
| <i class="fas fa-code mr-2 text-purple-400"></i> Input JSON | |
| </h2> | |
| <div class="flex space-x-2"> | |
| <button id="clearInput" class="text-gray-400 hover:text-white transition-colors" title="Clear"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| <button id="minifyBtn" class="text-gray-400 hover:text-white transition-colors" title="Minify"> | |
| <i class="fas fa-compress-alt"></i> | |
| </button> | |
| <button id="beautifyBtn" class="text-gray-400 hover:text-white transition-colors" title="Beautify"> | |
| <i class="fas fa-expand-alt"></i> | |
| </button> | |
| <button id="validateBtn" class="text-green-400 hover:text-green-300 transition-colors" title="Validate"> | |
| <i class="fas fa-check-circle"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="p-4 relative"> | |
| <textarea id="jsonInput" class="w-full bg-gray-700 border border-gray-600 rounded p-3 text-gray-200 focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none" placeholder='Paste your JSON here...'></textarea> | |
| <div id="inputError" class="text-red-400 text-sm mt-2 hidden"></div> | |
| </div> | |
| </div> | |
| <!-- Output Section --> | |
| <div class="bg-gray-800 rounded-lg shadow-lg overflow-hidden"> | |
| <div class="bg-gray-700 px-4 py-3 flex justify-between items-center"> | |
| <h2 class="font-semibold"> | |
| <i class="fas fa-eye mr-2 text-blue-400"></i> Formatted JSON | |
| </h2> | |
| <div class="flex space-x-2"> | |
| <button id="copyBtn" class="copy-btn text-gray-400 hover:text-white transition-colors" title="Copy to Clipboard"> | |
| <i class="fas fa-copy"></i> | |
| </button> | |
| <button id="downloadBtn" class="text-gray-400 hover:text-white transition-colors" title="Download"> | |
| <i class="fas fa-download"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="p-4"> | |
| <div id="formattedJson" class="w-full bg-gray-700 border border-gray-600 rounded p-3 overflow-auto"></div> | |
| <div id="validationResult" class="mt-4 p-3 rounded hidden"> | |
| <div class="flex items-start"> | |
| <div class="flex-shrink-0"> | |
| <i id="validationIcon" class="fas fa-check-circle text-2xl"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <h3 id="validationTitle" class="text-lg font-medium"></h3> | |
| <div id="validationDetails" class="mt-1 text-sm"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Sample JSON Buttons --> | |
| <div class="mt-8"> | |
| <h3 class="text-xl font-semibold mb-4 text-center">Try with sample JSON</h3> | |
| <div class="flex flex-wrap justify-center gap-3"> | |
| <button data-sample='{"name":"John","age":30,"city":"New York"}' class="sample-btn px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded transition-colors"> | |
| Simple Object | |
| </button> | |
| <button data-sample='[{"id":1,"name":"Alice","active":true},{"id":2,"name":"Bob","active":false},{"id":3,"name":"Charlie","active":true}]' class="sample-btn px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded transition-colors"> | |
| Array of Objects | |
| </button> | |
| <button data-sample='{"products":[{"id":"A1","price":19.99,"inStock":true},{"id":"B2","price":29.99,"inStock":false}],"totalItems":2}' class="sample-btn px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded transition-colors"> | |
| Nested Objects | |
| </button> | |
| <button data-sample='{"error":"Invalid JSON","line":3,"position":12}' class="sample-btn px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded transition-colors"> | |
| Invalid JSON | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Features Section --> | |
| <div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
| <div class="text-blue-400 text-2xl mb-3"> | |
| <i class="fas fa-check-double"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Real-time Validation</h3> | |
| <p class="text-gray-400">Instantly validate your JSON syntax and highlight errors with detailed messages.</p> | |
| </div> | |
| <div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
| <div class="text-purple-400 text-2xl mb-3"> | |
| <i class="fas fa-paint-brush"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Beautiful Formatting</h3> | |
| <p class="text-gray-400">Format your JSON with syntax highlighting, proper indentation and line breaks.</p> | |
| </div> | |
| <div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
| <div class="text-green-400 text-2xl mb-3"> | |
| <i class="fas fa-exchange-alt"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Convert & Minify</h3> | |
| <p class="text-gray-400">Easily convert between formatted and minified JSON with a single click.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const jsonInput = document.getElementById('jsonInput'); | |
| const formattedJson = document.getElementById('formattedJson'); | |
| const validateBtn = document.getElementById('validateBtn'); | |
| const beautifyBtn = document.getElementById('beautifyBtn'); | |
| const minifyBtn = document.getElementById('minifyBtn'); | |
| const clearInput = document.getElementById('clearInput'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const inputError = document.getElementById('inputError'); | |
| const validationResult = document.getElementById('validationResult'); | |
| const validationIcon = document.getElementById('validationIcon'); | |
| const validationTitle = document.getElementById('validationTitle'); | |
| const validationDetails = document.getElementById('validationDetails'); | |
| const sampleBtns = document.querySelectorAll('.sample-btn'); | |
| // Initialize with empty state | |
| clearInput.click(); | |
| // Sample JSON buttons | |
| sampleBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const sample = this.getAttribute('data-sample'); | |
| jsonInput.value = sample; | |
| formatAndValidate(); | |
| }); | |
| }); | |
| // Clear input | |
| clearInput.addEventListener('click', function() { | |
| jsonInput.value = ''; | |
| formattedJson.innerHTML = ''; | |
| inputError.classList.add('hidden'); | |
| validationResult.classList.add('hidden'); | |
| }); | |
| // Beautify JSON | |
| beautifyBtn.addEventListener('click', function() { | |
| formatAndValidate(true); | |
| }); | |
| // Minify JSON | |
| minifyBtn.addEventListener('click', function() { | |
| try { | |
| const jsonObj = JSON.parse(jsonInput.value); | |
| jsonInput.value = JSON.stringify(jsonObj); | |
| formatAndValidate(); | |
| } catch (e) { | |
| showError(e.message); | |
| } | |
| }); | |
| // Validate JSON | |
| validateBtn.addEventListener('click', function() { | |
| formatAndValidate(); | |
| }); | |
| // Copy to clipboard | |
| copyBtn.addEventListener('click', function() { | |
| if (!formattedJson.textContent.trim()) return; | |
| navigator.clipboard.writeText(formattedJson.textContent).then(() => { | |
| copyBtn.classList.add('copied'); | |
| copyBtn.innerHTML = '<i class="fas fa-check"></i>'; | |
| setTimeout(() => { | |
| copyBtn.classList.remove('copied'); | |
| copyBtn.innerHTML = '<i class="fas fa-copy"></i>'; | |
| }, 2000); | |
| }); | |
| }); | |
| // Download JSON | |
| downloadBtn.addEventListener('click', function() { | |
| if (!formattedJson.textContent.trim()) return; | |
| const blob = new Blob([formattedJson.textContent], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'formatted.json'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }); | |
| // Auto-format when typing (with debounce) | |
| let typingTimer; | |
| jsonInput.addEventListener('input', function() { | |
| clearTimeout(typingTimer); | |
| typingTimer = setTimeout(() => { | |
| formatAndValidate(); | |
| }, 800); | |
| }); | |
| // Main formatting and validation function | |
| function formatAndValidate(beautify = true) { | |
| inputError.classList.add('hidden'); | |
| validationResult.classList.add('hidden'); | |
| try { | |
| if (!jsonInput.value.trim()) { | |
| formattedJson.innerHTML = ''; | |
| return; | |
| } | |
| const jsonObj = JSON.parse(jsonInput.value); | |
| const formatted = beautify ? JSON.stringify(jsonObj, null, 2) : JSON.stringify(jsonObj); | |
| // Update input if we're minifying | |
| if (!beautify) { | |
| jsonInput.value = formatted; | |
| } | |
| // Syntax highlight | |
| formattedJson.innerHTML = syntaxHighlight(formatted); | |
| // Show validation success | |
| showValidationSuccess(jsonObj); | |
| } catch (e) { | |
| showError(e.message); | |
| } | |
| } | |
| // Show error message | |
| function showError(message) { | |
| inputError.textContent = message; | |
| inputError.classList.remove('hidden'); | |
| // Try to extract line number from error message | |
| const lineMatch = message.match(/position (\d+)/); | |
| if (lineMatch) { | |
| const position = parseInt(lineMatch[1]); | |
| highlightErrorLine(position); | |
| } | |
| // Show validation error | |
| validationResult.classList.remove('hidden'); | |
| validationResult.className = 'mt-4 p-3 rounded bg-red-900/30 border border-red-700'; | |
| validationIcon.className = 'fas fa-times-circle text-2xl text-red-400'; | |
| validationTitle.textContent = 'Invalid JSON'; | |
| validationDetails.innerHTML = `<p class="text-red-300">${message}</p>`; | |
| } | |
| // Show validation success | |
| function showValidationSuccess(jsonObj) { | |
| validationResult.classList.remove('hidden'); | |
| validationResult.className = 'mt-4 p-3 rounded bg-green-900/30 border border-green-700'; | |
| validationIcon.className = 'fas fa-check-circle text-2xl text-green-400'; | |
| validationTitle.textContent = 'Valid JSON'; | |
| // Count properties | |
| let propertyCount = 0; | |
| if (Array.isArray(jsonObj)) { | |
| propertyCount = jsonObj.length; | |
| validationDetails.innerHTML = ` | |
| <p class="text-green-300">✓ Valid JSON array with ${propertyCount} items</p> | |
| <p class="text-green-300 mt-1">✓ ${typeof jsonObj} (${getTypeDetails(jsonObj)})</p> | |
| `; | |
| } else if (jsonObj && typeof jsonObj === 'object') { | |
| propertyCount = Object.keys(jsonObj).length; | |
| validationDetails.innerHTML = ` | |
| <p class="text-green-300">✓ Valid JSON object with ${propertyCount} properties</p> | |
| <p class="text-green-300 mt-1">✓ ${typeof jsonObj} (${getTypeDetails(jsonObj)})</p> | |
| `; | |
| } else { | |
| validationDetails.innerHTML = ` | |
| <p class="text-green-300">✓ Valid JSON value</p> | |
| <p class="text-green-300 mt-1">✓ ${typeof jsonObj} (${getTypeDetails(jsonObj)})</p> | |
| `; | |
| } | |
| } | |
| // Get type details for validation message | |
| function getTypeDetails(value) { | |
| if (value === null) return 'null'; | |
| if (Array.isArray(value)) return 'array'; | |
| if (typeof value === 'object') return 'object'; | |
| return typeof value; | |
| } | |
| // Highlight error line in input | |
| function highlightErrorLine(position) { | |
| // This is a simplified approach - a more complete solution would need a proper textarea line calculator | |
| const lines = jsonInput.value.substr(0, position).split('\n'); | |
| const lineNumber = lines.length; | |
| // Scroll to the line | |
| const lineHeight = 20; // Approximate line height | |
| jsonInput.scrollTop = (lineNumber - 3) * lineHeight; | |
| // Highlight the line (this is a basic approach) | |
| inputError.textContent += ` (near line ${lineNumber})`; | |
| } | |
| // Syntax highlighting for JSON | |
| function syntaxHighlight(json) { | |
| if (!json) return ''; | |
| json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(match) { | |
| let cls = 'json-value'; | |
| let style = ''; | |
| if (/^"/.test(match)) { | |
| if (/:$/.test(match)) { | |
| cls = 'json-key'; | |
| match = match.replace(/:$/, ''); | |
| style = ' style="color: #9CDCFE;"'; | |
| } else { | |
| cls = 'json-string'; | |
| } | |
| } else if (/true|false/.test(match)) { | |
| cls = 'json-boolean'; | |
| } else if (/null/.test(match)) { | |
| cls = 'json-null'; | |
| } else if (/^-?\d+\.?\d*([eE][+\-]?\d+)?$/.test(match)) { | |
| cls = 'json-number'; | |
| } | |
| return '<span class="' + cls + '"' + style + '>' + match + '</span>'; | |
| }) | |
| .replace(/([{}[\],:])/g, function(match) { | |
| return '<span class="json-punctuation">' + match + '</span>'; | |
| }); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarkTheArtist/json-validator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |