Data
CSV ↔ JSON
Convert CSV to JSON or JSON to CSV. Supports nested/multilevel JSON flattening, custom delimiters, quoted fields, and drag & drop.
Drop a CSV file here
or click to browse · .csv, .tsv, .txt
JSON Output
—
Rows: 0
Columns: 0
Size: —
Drop a JSON file here
or click to browse · .json
CSV Output
—
Rows: 0
Columns: 0
Size: —
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
/* ── Tab switching ─────────────────────────────────────── */
function switchTab(tab) {
document.getElementById('panel-csv2json').classList.toggle('hidden', tab !== 'csv2json');
document.getElementById('panel-json2csv').classList.toggle('hidden', tab !== 'json2csv');
document.getElementById('tab-csv2json').classList.toggle('active', tab === 'csv2json');
document.getElementById('tab-json2csv').classList.toggle('active', tab === 'json2csv');
}
/* ── File drag & drop ──────────────────────────────────── */
function onDragOver(e, id) { e.preventDefault(); document.getElementById(id).classList.add('dragover'); }
function onDragLeave(id) { document.getElementById(id).classList.remove('dragover'); }
function onDrop(e, mode) {
e.preventDefault();
const id = mode === 'csv2json' ? 'dropZoneA' : 'dropZoneB';
document.getElementById(id).classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file) readFile(file, mode);
}
function loadFile(e, mode) {
const file = e.target.files[0];
if (file) readFile(file, mode);
}
function readFile(file, mode) {
const nameEl = document.getElementById(mode === 'csv2json' ? 'fileNameA' : 'fileNameB');
nameEl.textContent = file.name + ' (' + fmtBytes(file.size) + ')';
nameEl.style.display = 'block';
const reader = new FileReader();
reader.onload = e => {
if (mode === 'csv2json') {
document.getElementById('csvInput').value = e.target.result;
convertCSV();
} else {
document.getElementById('jsonInput').value = e.target.result;
convertJSON();
}
};
reader.readAsText(file);
}
/* ── CSV parser ────────────────────────────────────────── */
function parseCSV(text, delim) {
const rows = [];
let row = [], field = '', inQuotes = false;
for (let i = 0; i < text.length; i++) {
const ch = text[i], next = text[i + 1];
if (inQuotes) {
if (ch === '"' && next === '"') { field += '"'; i++; }
else if (ch === '"') { inQuotes = false; }
else { field += ch; }
} else {
if (ch === '"') { inQuotes = true; }
else if (ch === delim) { row.push(field); field = ''; }
else if (ch === '\n' || (ch === '\r' && next === '\n')) {
row.push(field); field = '';
if (row.some(c => c !== '')) rows.push(row);
row = [];
if (ch === '\r') i++;
} else if (ch === '\r') {
row.push(field); field = '';
if (row.some(c => c !== '')) rows.push(row);
row = [];
} else { field += ch; }
}
}
if (field !== '' || row.length) { row.push(field); if (row.some(c => c !== '')) rows.push(row); }
return rows;
}
/* ── CSV → JSON ────────────────────────────────────────── */
function convertCSV() {
const text = document.getElementById('csvInput').value.trim();
const delim = document.getElementById('csvDelim').value;
const useKeys = document.getElementById('firstRowKeys').checked;
const trim = document.getElementById('trimWhitespace').checked;
const minify = document.getElementById('minifyJson').checked;
const errEl = document.getElementById('csvError');
errEl.classList.add('hidden');
if (!text) { resetOutput('csv2json'); return; }
try {
const rows = parseCSV(text, delim);
if (!rows.length) throw new Error('No data found.');
let result, cols;
if (useKeys) {
const keys = rows[0].map(k => trim ? k.trim() : k);
cols = keys.length;
result = rows.slice(1).map(row => {
const obj = {};
keys.forEach((k, i) => { obj[k] = row[i] !== undefined ? (trim ? row[i].trim() : row[i]) : ''; });
return obj;
});
document.getElementById('statRowsA').textContent = result.length;
} else {
cols = rows[0].length;
result = trim ? rows.map(r => r.map(f => f.trim())) : rows;
document.getElementById('statRowsA').textContent = rows.length;
}
document.getElementById('statColsA').textContent = cols;
const json = minify ? JSON.stringify(result) : JSON.stringify(result, null, 2);
document.getElementById('jsonOutput').textContent = json;
document.getElementById('statSizeA').textContent = fmtBytes(new Blob([json]).size);
} catch (e) {
errEl.textContent = 'Error: ' + e.message;
errEl.classList.remove('hidden');
resetOutput('csv2json');
}
}
/* ── Flatten nested objects ──────────────────────────────── */
function flattenObject(obj, sep, prefix) {
const result = {};
for (const key of Object.keys(obj)) {
const fullKey = prefix ? prefix + sep + key : key;
const val = obj[key];
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
// Nested object — recurse
Object.assign(result, flattenObject(val, sep, fullKey));
} else if (Array.isArray(val)) {
if (val.length === 0) {
result[fullKey] = '';
} else if (val.every(v => v === null || typeof v !== 'object')) {
// All-primitive array — join as comma-separated string
result[fullKey] = val.map(v => v === null ? '' : String(v)).join(', ');
} else {
// Mixed or object array — flatten each element by index
val.forEach((item, i) => {
const indexKey = fullKey + sep + i;
if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
Object.assign(result, flattenObject(item, sep, indexKey));
} else if (Array.isArray(item)) {
// Nested array — JSON-stringify it
result[indexKey] = JSON.stringify(item);
} else {
result[indexKey] = item === null ? '' : item;
}
});
}
} else {
result[fullKey] = val;
}
}
return result;
}
/* ── JSON → CSV ────────────────────────────────────────── */
function convertJSON() {
const text = document.getElementById('jsonInput').value.trim();
const delim = document.getElementById('jsonDelim').value;
const header = document.getElementById('includeHeader').checked;
const quoteAll = document.getElementById('quoteAll').checked;
const flatten = document.getElementById('flattenNested').checked;
const nestSep = document.getElementById('nestSep').value;
const errEl = document.getElementById('jsonError');
errEl.classList.add('hidden');
errEl.style.cssText = '';
if (!text) { resetOutput('json2csv'); return; }
try {
let data = JSON.parse(text);
// Auto-unwrap common API wrapper shapes: {data:[...]}, {results:[...]}, etc.
const WRAPPER_KEYS = ['data','results','items','records','rows','users','list','entries','payload','response','content'];
if (!Array.isArray(data) && typeof data === 'object' && data !== null) {
const keys = Object.keys(data);
const wrapKey = keys.find(k => WRAPPER_KEYS.includes(k.toLowerCase()) && Array.isArray(data[k]));
if (wrapKey) {
data = data[wrapKey];
} else {
// Single object — wrap it
data = [data];
}
}
if (!Array.isArray(data)) throw new Error('JSON must be an array of objects or a recognized wrapper object.');
if (!data.length) throw new Error('JSON array is empty.');
// Filter out null/non-object items gracefully
const nullCount = data.filter(r => r === null || typeof r !== 'object' || Array.isArray(r)).length;
data = data.filter(r => r !== null && typeof r === 'object' && !Array.isArray(r));
if (!data.length) throw new Error('No valid object items found in array.');
// Flatten nested objects if enabled
if (flatten) {
data = data.map(row => flattenObject(row, nestSep, ''));
}
// Collect all keys (union across all rows, preserving insertion order)
const keysSet = new Set();
data.forEach(row => { Object.keys(row).forEach(k => keysSet.add(k)); });
const keys = [...keysSet];
if (!keys.length) throw new Error('Objects have no keys — nothing to convert.');
// Warn about skipped rows
if (nullCount > 0) {
errEl.textContent = `⚠️ ${nullCount} non-object item(s) in the array were skipped.`;
errEl.style.cssText = 'background:rgba(255,180,0,0.08);border-color:rgba(255,180,0,0.35);color:#e6b800;';
errEl.classList.remove('hidden');
}
function escapeField(val) {
let s;
if (val === null || val === undefined) {
s = '';
} else if (typeof val === 'object') {
// Arrays and objects: serialize to JSON string rather than [object Object]
s = JSON.stringify(val);
} else {
s = String(val);
}
const needsQuote = quoteAll || s.includes(delim) || s.includes('"') || s.includes('\n') || s.includes('\r');
if (needsQuote) return '"' + s.replace(/"/g, '""') + '"';
return s;
}
const lines = [];
if (header) lines.push(keys.map(k => escapeField(k)).join(delim));
data.forEach(row => {
lines.push(keys.map(k => escapeField(row[k])).join(delim));
});
const csv = lines.join('\n');
document.getElementById('csvOutput').textContent = csv;
document.getElementById('statRowsB').textContent = data.length;
document.getElementById('statColsB').textContent = keys.length;
document.getElementById('statSizeB').textContent = fmtBytes(new Blob([csv]).size);
} catch (e) {
errEl.textContent = 'Error: ' + e.message;
errEl.classList.remove('hidden');
resetOutput('json2csv');
}
}
/* ── Helpers ───────────────────────────────────────────── */
function resetOutput(mode) {
if (mode === 'csv2json') {
document.getElementById('jsonOutput').textContent = '—';
document.getElementById('statRowsA').textContent = '0';
document.getElementById('statColsA').textContent = '0';
document.getElementById('statSizeA').textContent = '—';
} else {
document.getElementById('csvOutput').textContent = '—';
document.getElementById('statRowsB').textContent = '0';
document.getElementById('statColsB').textContent = '0';
document.getElementById('statSizeB').textContent = '—';
}
}
function fmtBytes(b) {
if (b < 1024) return b + ' B';
if (b < 1024 * 1024) return (b / 1024).toFixed(1) + ' KB';
return (b / (1024 * 1024)).toFixed(2) + ' MB';
}
function copyOutput(outputId, btnId) {
const text = document.getElementById(outputId).textContent;
if (text === '—') return;
navigator.clipboard.writeText(text).catch(() => {});
const btn = document.getElementById(btnId);
btn.textContent = 'copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
}
function downloadOutput(outputId, filename, mime) {
const text = document.getElementById(outputId).textContent;
if (text === '—') return;
const blob = new Blob([text], { type: mime });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 5000);
}
function clearPanel(mode) {
if (mode === 'csv2json') {
document.getElementById('csvInput').value = '';
document.getElementById('csvError').classList.add('hidden');
document.getElementById('fileNameA').style.display = 'none';
document.getElementById('fileInputA').value = '';
} else {
document.getElementById('jsonInput').value = '';
document.getElementById('jsonError').classList.add('hidden');
document.getElementById('fileNameB').style.display = 'none';
document.getElementById('fileInputB').value = '';
}
resetOutput(mode);
}