json-validator / index.html
MarkTheArtist's picture
Add 2 files
f2bcf38 verified
<!DOCTYPE html>
<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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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>