CSV to JSON

Command: csv-to-json

Description: Convert CSV data to a JSON array of objects with camelCased property names.

Source Code

import { toCamelCase } from "../helpers.js";

function csvToJson(working) {
  const data = csvToObjects(working.text);
  return JSON.stringify(data, null, 2);
}

// Meta

csvToJson.title = "CSV to JSON";
csvToJson.description = "Convert CSV data to a JSON array of objects with camelCased property names.";
csvToJson.args = [];

// Helpers

// --- Low-level CSV → rows (array-of-arrays) -------------------------------
function parseCsvToRows(csvText) {
  const rows = [];
  let row = [];
  let field = "";
  let inQuotes = false;

  const text = String(csvText || "");
  const len = text.length;

  for (let i = 0; i < len; i++) {
    const char = text[i];

    if (char === '"') {
      if (inQuotes) {
        // If the next char is also a quote, it's an escaped quote
        if (i + 1 < len && text[i + 1] === '"') {
          field += '"';
          i++; // skip the next quote
        } else {
          // Closing quote
          inQuotes = false;
        }
      } else {
        // Opening quote (only if field is empty, or treat as literal if you want)
        inQuotes = true;
      }
    } else if (char === "," && !inQuotes) {
      // End of field
      row.push(field);
      field = "";
    } else if ((char === "\n" || char === "\r") && !inQuotes) {
      // End of row (handle CRLF / LF / CR)
      // If it's \r\n, we'll skip the \n in the next iteration
      if (char === "\r" && i + 1 < len && text[i + 1] === "\n") {
        i++;
      }
      row.push(field);
      field = "";
      rows.push(row);
      row = [];
    } else {
      field += char;
    }
  }

  // Last field / row (if any)
  if (field.length > 0 || row.length > 0) {
    row.push(field);
    rows.push(row);
  }

  return rows;
}

// --- High-level: CSV → array of objects with camelCased headers ----------
function csvToObjects(csvText) {
  const rows = parseCsvToRows(csvText);
  if (!rows.length) return [];

  const rawHeaders = rows[0];
  const headers = rawHeaders.map(toCamelCase);

  const dataRows = rows.slice(1);
  const objects = dataRows
    .filter((r) => r.some((cell) => String(cell).trim().length > 0)) // skip totally empty rows
    .map((row) => {
      const obj = {};
      for (let i = 0; i < headers.length; i++) {
        const key = headers[i];
        if (!key) continue; // skip empty header cells
        obj[key] = row[i] != null ? String(row[i]).trim() : "";
      }
      return obj;
    });

  return objects;
}

export default csvToJson;