Skip to content
返回博客
教程

CSV 与 JSON 互转:方法、陷阱与代码示例

学习如何用 Python、JavaScript 和 CLI 工具实现 CSV 与 JSON 互转。涵盖编码陷阱、类型强制转换、嵌套结构处理与大文件流式处理。

12 分钟

CSV 与 JSON 互转指南:方法、陷阱与最佳实践

运营团队给你发了一份 CSV 导出文件,而你的 API 只接受 JSON。打开文件一看,10,000 行逗号分隔的数据摆在面前,你不禁想:怎样才能最快地把 CSV 转成 JSON 而不丢失数据?

本指南涵盖四种转换方法(浏览器工具、JavaScript、Python、CLI)、反向转换(JSON 转 CSV)、五个会悄悄破坏数据的常见陷阱,以及如何处理无法完整载入内存的大文件。

CSV 与 JSON:何时使用哪种格式

在开始转换之前,先了解两种格式各自的优势。

维度CSVJSON
结构扁平表格(行和列)嵌套层级(对象、数组)
数据类型所有值都是字符串string、number、boolean、null
可读性电子表格友好开发者友好
主要用途数据导出/导入、报表、ETLAPI、配置文件、NoSQL 存储
文件大小更小(无重复键名)更大(每条记录都重复键名)
Schema隐式(表头行)显式(或使用 JSON Schema)

经验法则: 当数据是表格形式且使用方是电子表格或数据管道时,选择 CSV。当数据有层级结构或使用方是 API 时,选择 JSON。你可以随时用 JSON Formatter 验证输出的 JSON,尽早发现结构问题。

如果你的项目使用 JSON5 或 JSONC 等宽松 JSON 格式来管理配置,请参阅我们的 JSON5 与 JSONC 格式化指南,了解语法差异与工具支持。

4 种 CSV 转 JSON 的方法

方法 1 — 浏览器在线工具

对于一次性转换,浏览器工具是最快的途径。将 CSV 粘贴到在线转换器中,得到 JSON 输出,然后用 JSON Formatter 验证结果,确认结构正确。

优势在于:数据始终留在浏览器中。无需上传、无服务器处理、无隐私顾虑。当你处理内部数据、导出文件中嵌入的 API 密钥,或任何不希望发送到第三方服务器的内容时,这一点尤为重要。

最适合:小文件(10 MB 以下)、快速的一次性转换,以及非技术团队成员。

方法 2 — JavaScript / Node.js

浏览器端(原生 JS):

function csvToJson(csv) {
  const lines = csv.trim().split('\n');
  const headers = lines[0].split(',').map(h => h.trim());

  return lines.slice(1).map(line => {
    const values = line.split(',');
    return headers.reduce((obj, header, i) => {
      obj[header] = values[i]?.trim() ?? '';
      return obj;
    }, {});
  });
}

const csv = `name,age,city
Alice,30,New York
Bob,25,London`;

console.log(JSON.stringify(csvToJson(csv), null, 2));

这段代码适用于没有引号字段的简单 CSV。如果在生产环境中需要处理值内的逗号、字段中的换行符或带引号的字符串,请使用专业的解析器。

Node.js(csv-parser + 流):

import { createReadStream } from 'fs';
import { parse } from 'csv-parse';

const records = [];

createReadStream('data.csv')
  .pipe(parse({ columns: true, trim: true, skip_empty_lines: true }))
  .on('data', (row) => records.push(row))
  .on('end', () => {
    console.log(JSON.stringify(records, null, 2));
  });

columns: true 选项使用第一行作为键名。trim 选项去除值两端的空白字符。该方案能正确处理引号字段、转义逗号和多行值。

方法 3 — Python

标准库(零依赖):

import csv
import json

with open('data.csv', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    rows = list(reader)

with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(rows, f, indent=2, ensure_ascii=False)

csv.DictReader 使用表头行作为键,将每一行映射为字典。ensure_ascii=False 参数保留 Unicode 字符(中文、日文、带重音字符),而不是将它们转义为 \uXXXX

pandas(数据科学家的一行代码):

import pandas as pd

df = pd.read_csv('data.csv')
df.to_json('data.json', orient='records', indent=2, force_ascii=False)

如何选择:

  • csv + json:轻量级脚本、Lambda 函数、希望最小化依赖的容器环境。
  • pandas:当你在转换之前还需要清洗、过滤或转换数据时。在不仅仅是格式转换的场景下,导入 pandas 的开销是值得的。

方法 4 — CLI 工具

适用于 Shell 脚本和自动化管道:

csvkit:

# Install: pip install csvkit
csvjson data.csv > data.json

Miller (mlr):

# Install: brew install miller (macOS) or apt install miller (Ubuntu)
mlr --csv --json cat data.csv > data.json

配合 jq 进行过滤:

# Convert and filter in one pipeline
csvjson data.csv | jq '[.[] | select(.age | tonumber > 25)]'

Miller 尤为强大,因为它原生支持 CSV、JSON、TSV 等多种格式。你可以在转换过程中同时进行数据变换:

# Convert CSV to JSON, rename a field, add a computed field
mlr --csv --json rename name,fullName then put '$age_group = ($age > 30) ? "senior" : "junior"' data.csv

JSON 转 CSV:处理反向转换

将 JSON 转为 CSV 会引入正向转换中不存在的挑战。

展平嵌套对象

CSV 本质上是扁平的。当 JSON 包含嵌套对象时,你需要一种展平策略:

{
  "name": "Alice",
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}

转换后变为:

nameaddress.cityaddress.zip
AliceNew York10001

点号表示法(address.city)是最常见的方式。用 Python 实现:

import pandas as pd

data = [
    {"name": "Alice", "address": {"city": "New York", "zip": "10001"}},
    {"name": "Bob", "address": {"city": "London", "zip": "EC1A"}}
]

df = pd.json_normalize(data)
df.to_csv('output.csv', index=False)
# Columns: name, address.city, address.zip

处理数组

数组字段需要做出决策:

策略示例输入CSV 输出适用场景
拼接为字符串["admin","editor"]admin;editor简单列表,可重新导入
展开为多列["admin","editor"]role_0: admin, role_1: editor固定长度数组
展开为多行["admin","editor"]每个角色一行,共两行关系型分析

根据下游消费者的需求来选择。如果 CSV 最终要导回数据库,展开为多行通常最合理。

类型信息丢失

CSV 没有类型系统。将 JSON 转为 CSV 时:

  • true 变成字符串 "true" — 它是布尔值还是字符串?
  • null 变成空单元格 — 与空字符串 "" 无法区分
  • 42 变成 "42" — 它是数字还是字符串?

如果需要保证往返转换的保真度(CSV -> 处理 -> JSON),请在注释行或配套的 Schema 文件中记录你的类型约定。

5 个常见陷阱及其解决方法

这些问题会悄无声息地破坏数据。大多数教程都跳过了它们。不要等到生产环境中才学到教训。

1. 编码地雷

问题: 你打开同事发来的 CSV 文件,看到 é 而不是 é,或者 锟斤拷 而不是中文字符。

原因: 文件以某种编码(Windows-1252、GBK、Shift_JIS)保存,但解析器默认使用 UTF-8。Windows 上的 Excel 通常以 Windows-1252 保存 CSV,或者添加 UTF-8 BOM(字节顺序标记 — 文件开头不可见的 \xEF\xBB\xBF)。

解决方法:

# Detect encoding first
import chardet

with open('data.csv', 'rb') as f:
    result = chardet.detect(f.read(10000))
    print(result)  # {'encoding': 'Windows-1252', 'confidence': 0.73}

# Then read with the correct encoding
with open('data.csv', encoding=result['encoding']) as f:
    reader = csv.DictReader(f)
    # ...

在 Node.js 中,需要显式去除 BOM:

import { readFileSync } from 'fs';

let content = readFileSync('data.csv', 'utf-8');
// Strip UTF-8 BOM if present
if (content.charCodeAt(0) === 0xFEFF) {
  content = content.slice(1);
}

2. 分隔符混淆

问题: 解析器输出了一个巨大的单列,而不是多个字段。

原因: 在许多欧洲地区(法国、德国、西班牙),Excel 使用分号(;)作为 CSV 分隔符,因为逗号被用作小数点分隔符(例如 3,14 而非 3.14)。制表符分隔文件(.tsv)是另一种变体。

解决方法: 通过采样前几行自动检测分隔符:

import csv

with open('data.csv') as f:
    sample = f.read(8192)
    dialect = csv.Sniffer().sniff(sample, delimiters=',;\t|')
    f.seek(0)
    reader = csv.DictReader(f, dialect=dialect)

3. 前导零消失

问题: 邮政编码 00501 变成了 501。产品编码 007 变成了 7

原因: 解析器(或 Excel)将字段解释为数字并去掉了前导零。这对于邮政编码、电话号码和 ID 编码尤其危险。

解决方法: 强制字符串类型。在 pandas 中:

df = pd.read_csv('data.csv', dtype={'zip': str, 'product_code': str})

在 JavaScript 中,检查原始字符串是否与数值解析结果不同:

function preserveLeadingZeros(value) {
  if (/^0\d+$/.test(value)) return value; // Keep as string
  const num = Number(value);
  return isNaN(num) ? value : num;
}

4. 大数精度丢失

问题: ID 9007199254740993 在 JSON 中变成了 9007199254740992

原因: JavaScript 的 Number 是 64 位浮点数(IEEE 754)。超过 Number.MAX_SAFE_INTEGER(2^53 - 1 = 9007199254740991)的整数会丢失精度。这会影响数据库 ID、Snowflake ID 和 Twitter/X 推文 ID。

解决方法: 在 JSON 中将大数保留为字符串,或在处理代码中使用 BigInt:

// Parse with string preservation for large numbers
function safeParseNumber(value) {
  const num = Number(value);
  if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
    return value; // Keep as string to preserve precision
  }
  return isNaN(num) ? value : num;
}

5. 空值歧义

问题: CSV 中有空单元格。转换后,无法分辨原始值是空字符串 ""null,还是根本缺失。

原因: CSV 无法区分这三种状态。两个逗号之间的空字段(Alice,,30)可能表示其中任何一种。

解决方法: 定义约定并一致地执行:

def parse_value(value):
    if value == '':
        return None        # or '' — pick one convention
    if value == 'NULL' or value == 'null':
        return None
    return value

如果数据中使用了 NULLN/A- 等标记值,请明确记录并处理它们。

流式处理大文件

当 CSV 文件超过 100 MB 时,将其完整载入内存不是可行的方案。使用流式处理。

Node.js(流管道):

import { createReadStream, createWriteStream } from 'fs';
import { parse } from 'csv-parse';
import { Transform } from 'stream';
import { pipeline } from 'stream/promises';

let first = true;
const toJsonArray = new Transform({
  objectMode: true,
  transform(record, encoding, callback) {
    const prefix = first ? '[\n' : ',\n';
    first = false;
    callback(null, prefix + JSON.stringify(record));
  },
  flush(callback) {
    callback(null, '\n]');
  }
});

await pipeline(
  createReadStream('large.csv'),
  parse({ columns: true, trim: true }),
  toJsonArray,
  createWriteStream('large.json')
);

Python(生成器):

import csv
import json

def csv_rows(path):
    with open(path, encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield row

# Stream to JSON Lines format (one JSON object per line)
with open('large.jsonl', 'w', encoding='utf-8') as out:
    for row in csv_rows('large.csv'):
        out.write(json.dumps(row, ensure_ascii=False) + '\n')

对于超大文件,建议使用 JSON Lines(.jsonl)格式而非单个 JSON 数组。每一行都是一个独立的 JSON 对象,这意味着你也可以逐行处理输出文件,无需一次性解析整个文件。

常见问题

CSV 和 JSON 有什么区别?

CSV(Comma-Separated Values,逗号分隔值)以扁平表格形式存储数据,所有值都是字符串。JSON(JavaScript Object Notation)存储结构化数据,支持嵌套对象、数组以及类型化的值(字符串、数字、布尔值、null)。CSV 文件更小且对电子表格友好;JSON 表达能力更强且对 API 友好。

如何在 JavaScript 中将 CSV 转为 JSON?

在 Node.js 中使用 csv-parse 包:通过 createReadStream 读取文件,用管道传入 parse({ columns: true }),然后收集结果。在浏览器端,使用 FileReader 读取文件,按换行符分割,然后以表头行作为键名将各行映射为对象。

如何在 Python 中将 CSV 转为 JSON?

使用标准库中的 csv.DictReader 将行读取为字典,然后用 json.dump() 将其写为 JSON 数组。如果需要在转换前进行数据处理,pandas.read_csv() 加上 df.to_json(orient='records') 是一行代码的替代方案。

嵌套 JSON 可以转为 CSV 吗?

可以,但需要一种展平策略。最常见的方式是使用点号表示法:像 address.city 这样的字段会变成列标题。在 Python 中,pandas.json_normalize() 可以自动处理此问题。数组需要额外的决策 — 拼接为字符串、展开为多列,或展开为多行。

为什么 CSV 转换后出现乱码?

编码不匹配。文件可能以 Windows-1252 或 GBK 编码保存,但解析器默认使用 UTF-8。使用 chardet(Python)等检测库识别编码,然后在读取时显式指定。同时检查某些工具自动添加的 UTF-8 BOM。

如何处理超过 100 MB 的 CSV 文件?

使用流式处理,而非将整个文件载入内存。在 Node.js 中,通过流管道传入 csv-parse。在 Python 中,使用生成器配合 csv.DictReader 逐行迭代。建议输出 JSON Lines(.jsonl)格式而非单个 JSON 数组,以便下游更容易处理。

如何验证转换后的 JSON 是否有效?

将输出粘贴到在线 JSON Formatter 中,检查语法、结构和嵌套关系。如需自动化验证,在 JavaScript 中使用 JSON.parse(),在 Python 中使用 json.loads() — 两者都会对无效输入抛出明确的错误。如需 Schema 验证,定义 JSON Schema 并以编程方式进行校验。

要点总结

  1. 选择适合场景的方法:浏览器工具适合快速的一次性转换,代码适合自动化,CLI 适合管道处理。
  2. 警惕编码问题 — 始终显式指定编码,而非依赖默认值。
  3. 有意识地保留类型 — 前导零、大整数和 null 值都需要显式处理。
  4. 流式处理大文件,而非将其载入内存。对于超大数据集,考虑使用 JSON Lines 格式。
  5. 验证输出 — 将转换后的 JSON 通过 JSON Formatter 检查,在上线前捕获结构问题。