JSON ↔ CSV Converter — TypeScript source
Convert a JSON array of objects to CSV and back. Handles quoted fields, embedded commas, newlines and escaped quotes (RFC 4180). 100% in-browser.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON <-> CSV conversion (RFC 4180 quoting). Pure + deterministic - the
// unit-test surface for the JSON <-> CSV Converter tool. csvToRows is the raw
// parser (shared with csv-to-sql, which needs rows before headers: duplicate
// and empty header names are lossy through a Record).
function csvEscape(field: unknown): string {
const s = field == null ? '' : String(field);
if (/[",\n\r]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
return s;
}
export function jsonToCsv(json: string): string | null {
let data: unknown;
try { data = JSON.parse(json); } catch { return null; }
const rows = Array.isArray(data) ? data : [data];
const headers: string[] = [];
for (const row of rows) {
if (row && typeof row === 'object') {
for (const k of Object.keys(row as object)) if (!headers.includes(k)) headers.push(k);
}
}
if (headers.length === 0) return null;
const lines = [headers.map(csvEscape).join(',')];
for (const row of rows) {
const obj = row && typeof row === 'object' ? (row as Record<string, unknown>) : {};
lines.push(headers.map((h) => csvEscape(obj[h])).join(','));
}
return lines.join('\n');
}
export function csvToRows(csv: string): string[][] {
const rows: string[][] = [];
let field = '';
let row: string[] = [];
let inQ = false;
for (let i = 0; i < csv.length; i++) {
const ch = csv[i];
if (inQ) {
if (ch === '"') {
if (csv[i + 1] === '"') { field += '"'; i++; } else inQ = false;
} else field += ch;
} else if (ch === '"') {
inQ = true;
} else if (ch === ',') {
row.push(field); field = '';
} else if (ch === '\n') {
row.push(field); rows.push(row); row = []; field = '';
} else if (ch !== '\r') {
field += ch;
}
}
if (field.length > 0 || row.length > 0) { row.push(field); rows.push(row); }
return rows;
}
export function csvToJson(csv: string): Record<string, string>[] {
const rows = csvToRows(csv);
if (rows.length === 0) return [];
const headers = rows[0];
return rows.slice(1).map((r) => {
const obj: Record<string, string> = {};
headers.forEach((h, i) => { obj[h] = r[i] ?? ''; });
return obj;
});
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →