json / index.html
Ultronprime's picture
Add 3 files
0ed4351 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced JSON Data Viewer</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>
.data-type {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
margin-right: 4px;
}
.string { background-color: #93c5fd; color: #1e3a8a; }
.number { background-color: #86efac; color: #166534; }
.boolean { background-color: #fca5a5; color: #991b1b; }
.object { background-color: #d8b4fe; color: #5b21b6; }
.array { background-color: #fcd34d; color: #9a3412; }
.null { background-color: #9ca3af; color: #1f2937; }
.json-dropzone {
border: 2px dashed #ccc;
border-radius: 8px;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
cursor: pointer;
transition: all 0.2s;
}
.json-dropzone.active {
border-color: #3b82f6;
background-color: #f0f7ff;
}
.expand-icon {
transition: transform 0.2s;
cursor: pointer;
}
.expanded .expand-icon {
transform: rotate(90deg);
}
.tree-node {
margin-left: 16px;
border-left: 1px dashed #d1d5db;
padding-left: 8px;
}
.tree-node-header {
display: flex;
align-items: center;
padding: 4px 0;
cursor: pointer;
}
.tree-node-header:hover {
background-color: #f3f4f6;
}
.highlight-schema {
background-color: rgba(167, 243, 208, 0.3);
}
.sticky-header {
position: sticky;
top: 0;
background-color: white;
z-index: 10;
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold text-gray-800 mb-6">Advanced JSON Data Viewer</h1>
<!-- File Upload Section -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Upload JSON Files</h2>
<div
id="dropzone"
class="json-dropzone"
ondragover="event.preventDefault(); document.getElementById('dropzone').classList.add('active');"
ondragleave="event.preventDefault(); document.getElementById('dropzone').classList.remove('active');"
ondrop="event.preventDefault(); document.getElementById('dropzone').classList.remove('active'); handleFiles(event.dataTransfer.files);"
>
<i class="fas fa-file-upload text-4xl text-blue-500 mb-3"></i>
<p class="text-gray-600 mb-2">Drag & Drop JSON files here</p>
<p class="text-gray-400 text-sm mb-4">or</p>
<label for="fileInput" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md cursor-pointer transition">
<span>Select Files</span>
<input id="fileInput" type="file" accept=".json" multiple class="hidden" onchange="handleFiles(this.files)">
</label>
</div>
<div id="fileList" class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</div>
<!-- Schema Visualization & Processing Section -->
<div id="processingSection" class="hidden bg-white rounded-lg shadow-md p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-700">Schema Analysis</h2>
<div>
<button id="analyzeBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md transition">
<i class="fas fa-cog mr-2"></i> Analyze Schemas
</button>
</div>
</div>
<div class="flex mb-4">
<div class="w-1/2 pr-4">
<h3 class="text-lg font-medium text-gray-700 mb-2">Detected Fields</h3>
<div id="schemaTree" class="max-h-96 overflow-y-auto border border-gray-200 rounded-md p-2"></div>
</div>
<div class="w-1/2 pl-4">
<h3 class="text-lg font-medium text-gray-700 mb-2">Schema Details</h3>
<div id="schemaDetails" class="max-h-96 overflow-y-auto border border-gray-200 rounded-md p-2">
<p class="text-gray-500 text-center py-8">Select a field to view details</p>
</div>
</div>
</div>
<div class="flex items-center justify-center py-4" id="loadingIndicator">
<i class="fas fa-spinner fa-spin text-blue-500 text-3xl hidden"></i>
</div>
</div>
<!-- Data Table Section -->
<div id="resultSection" class="hidden bg-white rounded-lg shadow-md p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-700">Data View</h2>
<div class="flex space-x-2">
<div class="relative">
<select id="viewMode" class="appearance-none bg-gray-100 border border-gray-300 rounded-md px-3 py-2 pr-8">
<option value="table">Table View</option>
<option value="tree">Tree View</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i class="fas fa-chevron-down text-xs"></i>
</div>
</div>
<button id="exportBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded-md transition">
<i class="fas fa-file-export mr-2"></i> Export
</button>
<button id="clearBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md transition">
<i class="fas fa-trash mr-2"></i> Clear
</button>
</div>
</div>
<!-- Table View -->
<div id="tableView" class="overflow-x-auto">
<table id="resultTable" class="min-w-full border-collapse">
<thead>
<tr class="sticky-header border-b border-gray-200">
<th class="bg-gray-100 px-4 py-2 text-left text-gray-700">#</th>
<th class="bg-gray-100 px-4 py-2 text-left text-gray-700">Source</th>
<!-- Columns will be added dynamically -->
</tr>
</thead>
<tbody>
<!-- Data will be added dynamically -->
</tbody>
</table>
</div>
<!-- Tree View -->
<div id="treeView" class="hidden max-h-96 overflow-y-auto border border-gray-200 rounded-md p-2">
<!-- Data will be added dynamically -->
</div>
<!-- Summary Section -->
<div class="mt-6 p-4 bg-gray-50 rounded-md">
<h3 class="font-medium text-gray-700 mb-3">Schema Summary</h3>
<div id="summaryContent" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-white p-3 rounded-md shadow-sm">
<h4 class="text-sm font-medium text-gray-500 mb-1">Files Processed</h4>
<p id="fileCount" class="text-lg font-semibold text-gray-800">0</p>
</div>
<div class="bg-white p-3 rounded-md shadow-sm">
<h4 class="text-sm font-medium text-gray-500 mb-1">Total Records</h4>
<p id="recordCount" class="text-lg font-semibold text-gray-800">0</p>
</div>
<div class="bg-white p-3 rounded-md shadow-sm">
<h4 class="text-sm font-medium text-gray-500 mb-1">Unique Fields</h4>
<p id="fieldCount" class="text-lg font-semibold text-gray-800">0</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Global data store
const jsonData = {
files: [], // Array of loaded JSON files
schema: {}, // Combined schema of all JSON files
analysis: { // Results of schema analysis
fieldStats: {}, // Statistics about each field
commonFields: [],// List of fields common to all files
uniqueFields: [] // List of fields unique to specific files
},
records: [] // Flattened records for display
};
// Handle file selection/drop
function handleFiles(files) {
const fileListContainer = document.getElementById('fileList');
fileListContainer.innerHTML = '';
jsonData.files = [];
if (!files.length) return;
// Show processing section
document.getElementById('processingSection').classList.remove('hidden');
let filesLoaded = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
continue;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const content = JSON.parse(e.target.result);
jsonData.files.push({
name: file.name,
data: content,
schema: extractSchema(content)
});
// Add to file list display
const fileCard = document.createElement('div');
fileCard.className = 'bg-gray-50 p-3 rounded-md border border-gray-200 flex items-center justify-between';
fileCard.innerHTML = `
<div class="flex items-center">
<i class="fas fa-file-alt text-blue-400 mr-3"></i>
<span class="text-gray-700 font-medium truncate" title="${file.name}">${file.name}</span>
</div>
<span class="text-xs text-green-600 bg-green-100 px-2 py-1 rounded-full">Loaded</span>
`;
fileListContainer.appendChild(fileCard);
filesLoaded++;
// When all files are loaded, analyze them
if (filesLoaded === files.length) {
analyzeSchemas();
}
} catch (error) {
alert(`Error parsing ${file.name}: ${error.message}`);
}
};
reader.readAsText(file);
}
}
// Extract schema from JSON data
function extractSchema(data, prefix = '') {
const schema = {};
if (data === null) {
return { type: 'null', sample: null };
}
const type = Array.isArray(data) ? 'array' : typeof data;
if (type === 'object') {
schema.type = 'object';
schema.properties = {};
Object.keys(data).forEach(key => {
schema.properties[key] = extractSchema(data[key], prefix ? `${prefix}.${key}` : key);
});
}
else if (type === 'array') {
schema.type = 'array';
schema.items = data.length > 0 ? extractSchema(data[0], `${prefix}[]`) : { type: 'unknown' };
}
else {
schema.type = type;
schema.sample = data;
}
return schema;
}
// Analyze all loaded schemas
function analyzeSchemas() {
const loadingElement = document.getElementById('loadingIndicator').firstElementChild;
loadingElement.classList.remove('hidden');
// Reset analysis
jsonData.analysis = {
fieldStats: {},
commonFields: [],
uniqueFields: []
};
setTimeout(() => {
// Combine all schemas into one big schema
jsonData.schema = combineSchemas(jsonData.files.map(file => file.schema));
// Extract flattened field list
const allFields = flattenSchema(jsonData.schema);
// Calculate field statistics
allFields.forEach(field => {
jsonData.analysis.fieldStats[field.path] = {
types: new Set(),
samples: new Set(),
presentIn: new Set(),
path: field.path,
...field.info
};
});
// Calculate which files contain which fields
jsonData.files.forEach(file => {
const fileFields = flattenSchema(file.schema).map(f => f.path);
fileFields.forEach(fieldPath => {
jsonData.analysis.fieldStats[fieldPath].presentIn.add(file.name);
});
});
// Convert Sets to Arrays for easier display
Object.values(jsonData.analysis.fieldStats).forEach(stats => {
stats.types = Array.from(stats.types);
stats.samples = Array.from(stats.samples);
stats.presentIn = Array.from(stats.presentIn);
});
// Identify common and unique fields
const fileCount = jsonData.files.length;
Object.entries(jsonData.analysis.fieldStats).forEach(([path, stats]) => {
if (stats.presentIn.length === fileCount) {
jsonData.analysis.commonFields.push(path);
} else {
jsonData.analysis.uniqueFields.push({
path: path,
files: stats.presentIn
});
}
});
// Update UI
displaySchemaTree();
updateSummaryStats();
loadingElement.classList.add('hidden');
document.getElementById('resultSection').classList.remove('hidden');
}, 500);
}
// Combine multiple schemas into one
function combineSchemas(schemas) {
if (schemas.length === 0) return {};
if (schemas.length === 1) return schemas[0];
const combined = JSON.parse(JSON.stringify(schemas[0]));
for (let i = 1; i < schemas.length; i++) {
mergeSchema(combined, schemas[i]);
}
return combined;
}
// Merge two schemas
function mergeSchema(target, source) {
// If types are different, mark as union type
if (target.type !== source.type) {
if (!target.types) target.types = new Set([target.type]);
target.types.add(source.type);
target.type = 'mixed';
return;
}
// Handle objects
if (target.type === 'object' && source.type === 'object') {
// Merge properties
if (!target.properties) target.properties = {};
// Add all properties from source
Object.keys(source.properties).forEach(key => {
if (target.properties[key]) {
mergeSchema(target.properties[key], source.properties[key]);
} else {
target.properties[key] = source.properties[key];
}
});
}
// Handle arrays
else if (target.type === 'array' && source.type === 'array') {
if (target.items && source.items) {
mergeSchema(target.items, source.items);
} else if (source.items) {
target.items = source.items;
}
}
// Handle primitive types
else {
// Keep samples for primitive types
if (target.sample !== source.sample) {
if (!Array.isArray(target.samples)) {
target.samples = [target.sample];
}
target.samples.push(source.sample);
}
}
}
// Flatten schema to get all paths
function flattenSchema(schema, path = '', prefix = '', result = []) {
if (schema === null || typeof schema !== 'object') return result;
if (schema.type === 'object' && schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
const newPath = path ? `${path}.${key}` : key;
const newPrefix = prefix ? `${prefix} > ${key}` : key;
result.push({
path: newPath,
displayPath: newPrefix,
info: {
type: value.type,
sample: value.sample || (value.samples ? value.samples[0] : null),
possibleTypes: value.types ? Array.from(value.types) : [value.type],
samples: value.samples || (value.sample ? [value.sample] : [])
}
});
flattenSchema(value, newPath, newPrefix, result);
});
}
else if (schema.type === 'array' && schema.items) {
const newPath = path ? `${path}[]` : '[]';
const newPrefix = prefix ? `${prefix} > [item]` : '[item]';
result.push({
path: newPath,
displayPath: newPrefix,
info: {
type: `array<${schema.items.type}>`,
sample: schema.items.sample || (schema.items.samples ? schema.items.samples[0] : null),
possibleTypes: schema.items.types ? Array.from(schema.items.types) : [schema.items.type],
samples: schema.items.samples || (schema.items.sample ? [schema.items.sample] : [])
}
});
flattenSchema(schema.items, newPath, newPrefix, result);
}
else if (schema.type) {
result.push({
path: path,
displayPath: prefix,
info: {
type: schema.type,
sample: schema.sample || (schema.samples ? schema.samples[0] : null),
possibleTypes: schema.types ? Array.from(schema.types) : [schema.type],
samples: schema.samples || (schema.sample ? [schema.sample] : [])
}
});
}
return result;
}
// Display schema as a collapsible tree
function displaySchemaTree() {
const treeContainer = document.getElementById('schemaTree');
treeContainer.innerHTML = '';
const rootNode = document.createElement('div');
rootNode.className = 'tree-root';
// Create tree for each top-level property
const processNode = (schema, path, displayPath) => {
const node = document.createElement('div');
node.className = 'tree-node';
node.dataset.path = path;
const header = document.createElement('div');
header.className = 'tree-node-header';
if ((schema.type === 'object' && schema.properties && Object.keys(schema.properties).length > 0) ||
(schema.type === 'array' && schema.items && (schema.items.properties || schema.items.type !== 'unknown'))) {
// Node with children - make expandable
const expandIcon = document.createElement('i');
expandIcon.className = 'fas fa-chevron-right text-gray-400 text-xs mr-2 expand-icon';
header.appendChild(expandIcon);
header.onclick = (e) => {
e.stopPropagation();
node.classList.toggle('expanded');
if (node.classList.contains('expanded') && !node.children[1]) {
// Populate children on first expand
if (schema.type === 'object' && schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
node.appendChild(processNode(value, path ? `${path}.${key}` : key, `${displayPath} > ${key}`));
});
}
else if (schema.type === 'array' && schema.items) {
node.appendChild(processNode(schema.items, path ? `${path}[]` : '[]', `${displayPath} > [item]`));
}
}
};
}
// Add type indicator
const typeBadge = document.createElement('span');
typeBadge.className = 'data-type ' + (schema.type === 'array' ? schema.items.type : schema.type);
typeBadge.textContent = schema.type === 'array' ? `array<${schema.items.type}>` : schema.type;
// Add field name
const nameSpan = document.createElement('span');
const lastPart = displayPath.split(' > ').pop();
name
</html>