CSV files are everywhere — spreadsheet exports, database dumps, data feeds. JSON is the universal format for APIs, web apps, and modern data pipelines. Converting CSV to JSON is a task every developer runs into sooner or later, and it's trickier than it looks.
Why CSV parsing is harder than splitting on commas
The naive approach — line.split(",") — breaks immediately on real-world data:
name,bio,score
Alice,"Developer, designer",42
Bob,"Says ""hello""",17
Two problems here:
- Quoted fields can contain commas —
"Developer, designer"is one value, not two. - Escaped quotes use double-quote syntax —
""inside a quoted field means a literal".
The correct parser must handle both. Use a battle-tested library whenever one is available.
The conversion algorithm
A correct CSV-to-JSON converter does four things:
- Split lines — handle both
\n(Unix) and\r\n(Windows) line endings. - Parse each line — respect quoted fields and escaped quotes (RFC 4180).
- Map columns to keys — use the first row as header names.
- Return an array of objects — one object per data row.
Type coercion: strings vs numbers
CSV has no types — everything is a string. You have two choices:
| Option | Result | Best for |
|---|---|---|
| String-only | { "score": "42" } |
Safe, no data loss |
| Auto-coerce | { "score": 42 } |
APIs, databases, analytics |
Auto-coercion converts numeric strings to numbers and "true"/"false" to booleans. Use it carefully — a column named phone with value "0123456789" should stay a string.
Quick reference
| Input | Parsed as | Notes |
|---|---|---|
Alice |
"Alice" |
Plain string |
"Alice, Smith" |
"Alice, Smith" |
Quoted field with comma |
"Says ""hi""" |
"Says \"hi\"" |
Escaped quote |
42 |
42 (if coercing) |
Numeric string |
Alice |
"Alice" |
Trim whitespace |
| (empty) | null or "" |
Empty field |
JavaScript
Browser / Node.js (manual parser)
function csvToJson(csv, { coerce = false } = {}) {
const lines = csv.replace(/\r\n/g, "\n").split("\n").filter(Boolean);
if (lines.length === 0) return [];
const headers = parseRow(lines[0]);
return lines.slice(1).map((line) => {
const values = parseRow(line);
return Object.fromEntries(
headers.map((key, i) => [key.trim(), coerceValue(values[i] ?? "", coerce)])
);
});
}
function parseRow(line) {
const fields = [];
let field = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (inQuotes) {
if (ch === '"' && line[i + 1] === '"') {
field += '"';
i++; // skip second quote
} else if (ch === '"') {
inQuotes = false;
} else {
field += ch;
}
} else {
if (ch === '"') {
inQuotes = true;
} else if (ch === ",") {
fields.push(field);
field = "";
} else {
field += ch;
}
}
}
fields.push(field);
return fields;
}
function coerceValue(val, coerce) {
if (!coerce) return val;
if (val === "") return null;
if (val === "true") return true;
if (val === "false") return false;
const num = Number(val);
return isNaN(num) ? val : num;
}
// Usage
const csv = `name,score,active
Alice,42,true
Bob,17,false`;
console.log(csvToJson(csv, { coerce: true }));
// [{ name: "Alice", score: 42, active: true }, { name: "Bob", score: 17, active: false }]
Node.js with a library (recommended for production)
import { parse } from "csv-parse/sync";
const csv = `name,score
Alice,42
Bob,17`;
const records = parse(csv, {
columns: true, // use first row as keys
skip_empty_lines: true,
cast: true, // auto-coerce numbers/booleans
trim: true,
});
console.log(JSON.stringify(records, null, 2));
Install: npm install csv-parse
Python
Standard library (csv module)
import csv
import json
import io
def csv_to_json(csv_text: str, coerce: bool = False) -> list[dict]:
reader = csv.DictReader(io.StringIO(csv_text))
rows = []
for row in reader:
if coerce:
row = {k: coerce_value(v) for k, v in row.items()}
rows.append(dict(row))
return rows
def coerce_value(val: str):
if val == "":
return None
if val.lower() == "true":
return True
if val.lower() == "false":
return False
try:
return int(val)
except ValueError:
pass
try:
return float(val)
except ValueError:
pass
return val
csv_text = """name,score,active
Alice,42,true
Bob,17,false"""
result = csv_to_json(csv_text, coerce=True)
print(json.dumps(result, indent=2))
pandas (for data science workflows)
import pandas as pd
import json
df = pd.read_csv("data.csv")
result = json.loads(df.to_json(orient="records"))
print(json.dumps(result, indent=2))
pandas handles type inference automatically — numeric columns become int64/float64, not strings.
Go
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
)
func csvToJSON(csvText string) ([]map[string]any, error) {
r := csv.NewReader(strings.NewReader(csvText))
r.TrimLeadingSpace = true
records, err := r.ReadAll()
if err != nil {
return nil, err
}
if len(records) == 0 {
return nil, nil
}
headers := records[0]
rows := make([]map[string]any, 0, len(records)-1)
for _, record := range records[1:] {
obj := make(map[string]any, len(headers))
for i, key := range headers {
val := ""
if i < len(record) {
val = record[i]
}
obj[key] = coerce(val)
}
rows = append(rows, obj)
}
return rows, nil
}
func coerce(s string) any {
if s == "" {
return nil
}
if s == "true" {
return true
}
if s == "false" {
return false
}
if n, err := strconv.Atoi(s); err == nil {
return n
}
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
}
return s
}
func main() {
input := "name,score,active\nAlice,42,true\nBob,17,false"
rows, err := csvToJSON(input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
out, _ := json.MarshalIndent(rows, "", " ")
fmt.Println(string(out))
}
Go's encoding/csv handles quoting and escaping automatically — you only need to build the header→value mapping yourself.
PHP
<?php
function csvToJson(string $csv, bool $coerce = false): string {
$lines = array_filter(explode("\n", str_replace("\r\n", "\n", $csv)));
$lines = array_values($lines);
if (count($lines) === 0) return '[]';
$headers = str_getcsv($lines[0]);
$rows = [];
foreach (array_slice($lines, 1) as $line) {
$values = str_getcsv($line);
$row = [];
foreach ($headers as $i => $key) {
$val = $values[$i] ?? '';
$row[trim($key)] = $coerce ? coerceValue($val) : $val;
}
$rows[] = $row;
}
return json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
function coerceValue(string $val): mixed {
if ($val === '') return null;
if (strtolower($val) === 'true') return true;
if (strtolower($val) === 'false') return false;
if (is_numeric($val)) return $val + 0; // int or float
return $val;
}
$csv = "name,score,active\nAlice,42,true\nBob,17,false";
echo csvToJson($csv, coerce: true);
PHP's str_getcsv() handles RFC 4180 quoting correctly — don't use explode(",", $line).
Handling edge cases
Empty fields
An empty CSV cell (,,) should produce null (if coercing) or "" (string-only mode). Never silently drop the key.
Missing columns
If a row has fewer columns than the header, fill the missing values with null rather than throwing an error.
BOM (Byte Order Mark)
Excel-generated CSV files sometimes start with a UTF-8 BOM (\xEF\xBB\xBF). Strip it before parsing:
csv = csv.replace(/^\uFEFF/, "");
Windows line endings
Always normalise \r\n → \n before splitting lines, or use a proper CSV library that handles it.
Large files
For files over 50 MB, use streaming parsers instead of loading the whole file into memory:
- Node.js:
csv-parsewith streams - Python: iterate over
csv.DictReaderwithout.read()first - Go: call
r.Read()in a loop instead ofr.ReadAll()
Frequently asked questions
Does CSV have a standard?
Yes — RFC 4180. It defines quoting, escaping, and line endings. Real-world CSV often deviates (semicolons as delimiters in European locales, tabs in TSV), so a good parser should let you configure the delimiter.
What delimiter should I use?
Comma (,) is the default. Semicolons (;) are common in European countries where commas are used as decimal separators. Tabs (\t) create TSV (Tab-Separated Values). Always detect or let the user specify.
How do I handle CSV with a semicolon delimiter?
Pass delimiter: ";" to your CSV library, or replace ; with , in a pre-processing step (only safe if no quoted fields contain semicolons).
Why does my JSON have all string values?
CSV has no types — everything is a string by default. Enable type coercion in your parser to convert "42" → 42 and "true" → true.
How do I convert a CSV file (not a string)?
Read the file first: fs.readFileSync("data.csv", "utf8") in Node, open("data.csv") in Python, os.Open("data.csv") in Go. Then pass the string/reader to your parser.
Can I convert CSV to JSON in the browser?
Yes — the File API lets you read local files: use FileReader.readAsText(file) to get the CSV string, then parse it with the JavaScript example above.