JSON to CSV and Back: A Developer's Complete Guide

· 10 min read

何时使用 JSON 与 CSV

在现代软件开发中,JSON 和 CSV 是两种最常用的数据交换格式。理解它们各自的优势和适用场景对于做出正确的技术决策至关重要。

JSON 的优势

JSON (JavaScript Object Notation) 是一种灵活的数据格式,特别适合以下场景:

CSV 的优势

CSV (Comma-Separated Values) 是一种简单的表格数据格式,在以下场景中表现出色:

何时进行转换

以下是一些常见的转换场景:

Python 中的 JSON 到 CSV 转换

Python 提供了多种方式来处理 JSON 到 CSV 的转换。我们将探讨两种主要方法:使用 pandas 库和使用标准库的 csv 模块。

使用 Pandas 进行转换

Pandas 是处理数据转换的首选工具,它提供了简洁而强大的 API:

import pandas as pd
import json

# 从文件读取 JSON
with open('data.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# 转换为 DataFrame
df = pd.DataFrame(data)

# 导出为 CSV
df.to_csv('output.csv', index=False, encoding='utf-8')

# 如果 JSON 是嵌套的,可以使用 json_normalize
from pandas import json_normalize

with open('nested_data.json', 'r', encoding='utf-8') as f:
    nested_data = json.load(f)

df = json_normalize(nested_data)
df.to_csv('normalized_output.csv', index=False, encoding='utf-8')

处理 JSON 数组

当 JSON 文件包含对象数组时,pandas 可以轻松处理:

import pandas as pd

# JSON 数组示例
json_data = '''
[
  {"name": "张三", "age": 28, "city": "北京"},
  {"name": "李四", "age": 32, "city": "上海"},
  {"name": "王五", "age": 25, "city": "深圳"}
]
'''

# 直接从 JSON 字符串读取
df = pd.read_json(json_data)

# 或者从 URL 读取
# df = pd.read_json('https://api.example.com/users')

# 导出为 CSV
df.to_csv('users.csv', index=False, encoding='utf-8-sig')  # utf-8-sig 用于 Excel 兼容性

使用 CSV 模块进行转换

对于更细粒度的控制,可以使用 Python 的标准 csv 模块:

import json
import csv

# 读取 JSON 文件
with open('data.json', 'r', encoding='utf-8') as json_file:
    data = json.load(json_file)

# 确保数据是列表格式
if not isinstance(data, list):
    data = [data]

# 获取所有可能的字段名
fieldnames = set()
for item in data:
    fieldnames.update(item.keys())
fieldnames = sorted(fieldnames)

# 写入 CSV
with open('output.csv', 'w', newline='', encoding='utf-8') as csv_file:
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(data)

自定义转换逻辑

有时需要在转换过程中进行数据清理或转换:

import json
import csv
from datetime import datetime

def convert_json_to_csv(json_path, csv_path):
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    # 数据转换函数
    def transform_row(row):
        # 转换日期格式
        if 'created_at' in row:
            row['created_at'] = datetime.fromisoformat(
                row['created_at'].replace('Z', '+00:00')
            ).strftime('%Y-%m-%d %H:%M:%S')
        
        # 转换布尔值
        for key, value in row.items():
            if isinstance(value, bool):
                row[key] = 'Yes' if value else 'No'
        
        return row
    
    # 应用转换
    transformed_data = [transform_row(row.copy()) for row in data]
    
    # 写入 CSV
    if transformed_data:
        fieldnames = transformed_data[0].keys()
        with open(csv_path, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(transformed_data)

convert_json_to_csv('input.json', 'output.csv')

JavaScript/Node.js 中的 JSON 到 CSV 转换

在 JavaScript 和 Node.js 环境中,有多种方法可以实现 JSON 到 CSV 的转换。

使用 json2csv 库

json2csv 是 Node.js 中最流行的转换库:

const { Parser } = require('json2csv');
const fs = require('fs');

// 示例数据
const data = [
  { name: '张三', age: 28, city: '北京', salary: 50000 },
  { name: '李四', age: 32, city: '上海', salary: 60000 },
  { name: '王五', age: 25, city: '深圳', salary: 55000 }
];

// 配置字段
const fields = ['name', 'age', 'city', 'salary'];
const opts = { fields };

try {
  const parser = new Parser(opts);
  const csv = parser.parse(data);
  
  // 写入文件
  fs.writeFileSync('output.csv', csv, 'utf8');
  console.log('CSV 文件已生成');
} catch (err) {
  console.error('转换错误:', err);
}

自定义字段和格式化

json2csv 支持高级配置选项:

const { Parser } = require('json2csv');
const fs = require('fs');

const data = [
  { 
    name: '张三', 
    age: 28, 
    salary: 50000,
    joinDate: '2020-01-15',
    active: true
  },
  { 
    name: '李四', 
    age: 32, 
    salary: 60000,
    joinDate: '2019-06-20',
    active: false
  }
];

// 自定义字段配置
const fields = [
  { label: '姓名', value: 'name' },
  { label: '年龄', value: 'age' },
  { 
    label: '月薪', 
    value: (row) => `¥${row.salary.toLocaleString()}`
  },
  { 
    label: '入职日期', 
    value: 'joinDate',
    default: 'N/A'
  },
  {
    label: '状态',
    value: (row) => row.active ? '在职' : '离职'
  }
];

const opts = { 
  fields,
  quote: '"',
  delimiter: ',',
  header: true
};

const parser = new Parser(opts);
const csv = parser.parse(data);

fs.writeFileSync('formatted_output.csv', csv, 'utf8');

浏览器端转换

在浏览器中,可以使用纯 JavaScript 实现转换:

function jsonToCsv(jsonData) {
  if (!jsonData || jsonData.length === 0) {
    return '';
  }
  
  // 获取所有键作为表头
  const headers = Object.keys(jsonData[0]);
  
  // 转义 CSV 值
  const escapeCsvValue = (value) => {
    if (value === null || value === undefined) {
      return '';
    }
    const stringValue = String(value);
    if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
      return `"${stringValue.replace(/"/g, '""')}"`;
    }
    return stringValue;
  };
  
  // 构建 CSV 字符串
  const csvRows = [];
  
  // 添加表头
  csvRows.push(headers.map(escapeCsvValue).join(','));
  
  // 添加数据行
  for (const row of jsonData) {
    const values = headers.map(header => escapeCsvValue(row[header]));
    csvRows.push(values.join(','));
  }
  
  return csvRows.join('\n');
}

// 下载 CSV 文件
function downloadCsv(jsonData, filename = 'data.csv') {
  const csv = jsonToCsv(jsonData);
  const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
  const link = document.createElement('a');
  
  if (link.download !== undefined) {
    const url = URL.createObjectURL(blob);
    link.setAttribute('href', url);
    link.setAttribute('download', filename);
    link.style.visibility = 'hidden';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
}

// 使用示例
const data = [
  { name: '张三', age: 28, city: '北京' },
  { name: '李四', age: 32, city: '上海' }
];

downloadCsv(data, 'users.csv');

使用 Stream 处理大文件

对于大型 JSON 文件,使用流式处理可以避免内存问题:

const { Transform } = require('stream');
const fs = require('fs');
const JSONStream = require('JSONStream');

// 创建转换流
class JsonToCsvTransform extends Transform {
  constructor(options = {}) {
    super({ objectMode: true });
    this.headers = options.headers || [];
    this.headerWritten = false;
  }
  
  _transform(chunk, encoding, callback) {
    try {
      // 写入表头
      if (!this.headerWritten) {
        if (this.headers.length === 0) {
          this.headers = Object.keys(chunk);
        }
        this.push(this.headers.join(',') + '\n');
        this.headerWritten = true;
      }
      
      // 写入数据行
      const values = this.headers.map(header => {
        const value = chunk[header];
        if (value === null || value === undefined) return '';
        const str = String(value);
        return str.includes(',') ? `"${str.replace(/"/g, '""')}"` : str;
      });
      
      this.push(values.join(',') + '\n');
      callback();
    } catch (err) {
      callback(err);
    }
  }
}

// 使用流式转换
fs.createReadStream('large_data.json')
  .pipe(JSONStream.parse('*'))
  .pipe(new JsonToCsvTransform())
  .pipe(fs.createWriteStream('output.csv'))
  .on('finish', () => console.log('转换完成'))
  .on('error', (err) => console.error('转换错误:', err));

CSV 到 JSON 的转换

反向转换同样重要,特别是当需要将电子表格数据导入到 Web 应用或 API 时。

Python 中的 CSV 到 JSON

使用 pandas 进行转换非常简单:

import pandas as pd

# 读取 CSV
df = pd.read_csv('input.csv', encoding='utf-8')

# 转换为 JSON
# 方式 1: 记录列表格式
json_records = df.to_json(orient='records', force_ascii=False, indent=2)

# 方式 2: 列导向格式
json_columns = df.to_json(orient='columns', force_ascii=False, indent=2)

# 方式 3: 索引导向格式
json_index = df.to_json(orient='index', force_ascii=False, indent=2)

# 写入文件
with open('output.json', 'w', encoding='utf-8') as f:
    f.write(json_records)

# 或者直接写入
df.to_json('output.json', orient='records', force_ascii=False, indent=2)

使用 CSV 模块转换

对于更精细的控制,可以使用标准库:

import csv
import json

def csv_to_json(csv_path, json_path):
    data = []
    
    with open(csv_path, 'r', encoding='utf-8') as csv_file:
        csv_reader = csv.DictReader(csv_file)
        
        for row in csv_reader:
            # 数据类型转换
            processed_row = {}
            for key, value in row.items():
                # 尝试转换为数字
                try:
                    if '.' in value:
                        processed_row[key] = float(value)
                    else:
                        processed_row[key] = int(value)
                except ValueError:
                    # 保持为字符串
                    processed_row[key] = value
            
            data.append(processed_row)
    
    with open(json_path, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, ensure_ascii=False, indent=2)

csv_to_json('input.csv', 'output.json')

Node.js 中的 CSV 到 JSON

使用 csv-parser 库可以轻松实现:

const fs = require('fs');
const csv = require('csv-parser');

const results = [];

fs.createReadStream('input.csv')
  .pipe(csv())
  .on('data', (data) => {
    // 数据类型转换
    const processed = {};
    for (const [key, value] of Object.entries(data)) {
      // 尝试转换数字
      const num = Number(value);
      processed[key] = !isNaN(num) && value !== '' ? num : value;
    }
    results.push(processed);
  })
  .on('end', () => {
    fs.writeFileSync('output.json', JSON.stringify(results, null, 2), 'utf8');
    console.log('CSV 已成功转换为 JSON');
  })
  .on('error', (err) => {
    console.error('转换错误:', err);
  });

浏览器端 CSV 到 JSON

在浏览器中处理用户上传的 CSV 文件:

function csvToJson(csvText) {
  const lines = csvText.split('\n').filter(line => line.trim());
  if (lines.length === 0) return [];
  
  const headers = lines[0].split(',').map(h => h.trim());
  const result = [];
  
  for (let i = 1; i < lines.length; i++) {
    const obj = {};
    const currentLine = lines[i].split(',');
    
    headers.forEach((header, index) => {
      let value = currentLine[index] ? currentLine[index].trim() : '';
      
      // 移除引号
      if (value.startsWith('"') && value.endsWith('"')) {
        value = value.slice(1, -1).replace(/""/g, '"');
      }
      
      // 尝试转换为数字
      const num = Number(value);
      obj[header] = !isNaN(num) && value !== '' ? num : value;
    });
    
    result.push(obj);
  }
  
  return result;
}

// 处理文件上传
document.getElementById('csvFile').addEventListener('change', (event) => {
  const file = event.target.files[0];
  const reader = new FileReader();
  
  reader.onload = (e) => {
    const csvText = e.target.result;
    const jsonData = csvToJson(csvText);
    console.log('转换后的 JSON:', jsonData);
    
    // 下载 JSON 文件
    const blob = new Blob([JSON.stringify(jsonData, null, 2)], 
      { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'output.json';
    a.click();
  };
  
  reader.readAsText(file);
});

处理嵌套的 JSON 对象

嵌套的 JSON 结构是转换为 CSV 时最具挑战性的问题之一。CSV 是扁平的表格格式,而 JSON 可以有任意深度的嵌套。

扁平化策略

有几种常见的扁平化策略:

Python 中的扁平化

使用 pandas 的 json_normalize 函数:

import pandas as pd
from pandas import json_normalize

# 嵌套 JSON 示例
nested_data = [
  {
    "id": 1,
    "name": "张三",
    "address": {
      "city": "北京",
      "district": "朝阳区",
      "street": "建国路"
    },
    "contacts": {
      "email": "[email protected]",
      "phone": "13800138000"
    }
  },
  {
    "id": 2,
    "name": "李四",
    "address": {
      "city": "上海",
      "district": "浦东新区",
      "street": "世纪大道"
    },
    "contacts": {
      "email": "[email protected]",
      "phone": "13900139000"
    }
  }
]

# 扁平化嵌套结构
df = json_normalize(nested_data, sep='_')
print(df.columns)
# 输出: ['id', 'name', 'address_city', 'address_district', 'address_street', 
#        'contacts_email', 'contacts_phone']

df.to_csv('flattened.csv', index=False, encoding='utf-8')

自定义扁平化函数

对于更复杂的场景,可以编写自定义扁平化逻辑:

def flatten_json(nested_json, separator='_', prefix=''):
    """
    递归扁平化嵌套的 JSON 对象
    """
    flattened = {}
    
    for key, value in nested_json.items():
        new_key = f"{prefix}{separator}{key}" if prefix else key
        
        if isinstance(value, dict):
            # 递归处理嵌套字典
            flattened.update(flatten_json(value, separator, new_key))
        elif isinstance(value, list):
            # 处理列表:转换为 JSON 字符串或展开
            if len(value) > 0 and isinstance(value[0], dict):
                # 如果是对象列表,转换为 JSON 字符串
                import json
                flattened[new_key] = json.dumps(value, ensure_ascii=False)
            else:
                # 如果是简单值列表,用分号连接
                flattened[new_key] = ';'.join(map(str, value))
        else:
            flattened[new_key] = value
    
    return flattened

# 使用示例
nested_data = {
    "user": {
        "id": 1,
        "profile": {
            "name": "张三",
            "age": 28
        },
        "settings": {
            "theme": "dark",
            "notifications": {
                "email": True,
                "sms": False
            }
        }
    }
}

flattened = flatten_json(nested_data)
print(flattened)
# 输出: {
#   'user_id': 1,
#   'user_profile_name': '张三',
#   'user_profile_age': 28,
#   'user_settings_theme': 'dark',
#   'user_settings_notifications_email': True,
#   'user_settings_notifications_sms': False
# }

JavaScript 中的扁平化

在 Node.js 中实现类似的扁平化逻辑:

function flattenObject(obj, prefix = '', separator = '_') {
  const flattened = {};
  
  for (const [key, value] of Object.entries(obj)) {
    const newKey = prefix ? `${prefix}${separator}${key}` : key;
    
    if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
      // 递归处理嵌套对象
      Object.assign(flattened, flattenObject(value, newKey, separator));
    } else if (Array.isArray(value)) {
      // 处理数组
      if (value.length > 0 && typeof value[0] === 'object') {
        flattened[newKey] = JSON.stringify(value);
      } else {
        flattened[newKey] = value.join(';');
      }
    } else {
      flattened[newKey] = value;
    }
  }
  
  return flattened;
}

// 使用 json2csv 与扁平化
const { Parser } = require('json2csv');
const fs = require('fs');

const nestedData = [
  {
    id: 1,
    user: {
      name: '张三',
      contact: {
        email: '[email protected]',
        phone: '13800138000'
      }
    },
    metadata: {
      created: '2024-01-01',
      updated: '2024-01-15'
    }
  }
];

// 扁平化所有记录
const flattenedData = nestedData.map(item => flattenObject(item));

const parser = new Parser();
const csv = parser.parse(flattenedData);

fs.writeFileSync('flattened_output.csv', csv, 'utf8');

处理深度嵌套的最佳实践

对于非常深的嵌套结构,考虑以下策略:

import pandas as pd
import json

def smart_flatten(data, max_depth=3):
    """
    智能扁平化:限制深度,超过深度的转为 JSON 字符串
    """
    def flatten_recursive(obj, prefix='', depth=0):
        flattened = {}
        
        if depth >= max_depth:
            # 超过最大深度,转为 JSON 字符串
            return {prefix.rstrip('_'): json.dumps(obj, ensure_ascii=False)}
        
        for key, value in obj.items():
            new_key = f"{prefix}{key}"
            
            if isinstance(value, dict):
                flattened.update(flatten_recursive(value, f"{new_key}_", depth + 1))
            else:
                flattened[new_key] = value
        
        return flattened
    
    if isinstance(data, list):
        return [flatten_recursive(item) for item in data]
    else:
        return flatten_recursive(data)

# 使用示例
deep_nested = [
    {
        "level1": {
            "level2": {
                "level3": {
                    "level4": {
                        "value": "too deep"
                    }
                }
            }
        }
    }
]

result = smart_flatten(deep_nested, max_depth=2)
df = pd.DataFrame(result)
df.to_csv('smart_flattened.csv', index=False)

处理 JSON 中的数组

数组是 JSON 中常见的结构,但在 CSV 中表示数组需要特殊处理。

数组处理策略

有几种方法可以处理 JSON 数组:

  1. 连接为字符串:使用分隔符(如分号或竖线)连接数组元素
  2. 创建多行:为数组的每个元素创建一行,重复父级数据
  3. 创建多列:为数组的每个索引创建一列
  4. JSON 字符串化:将整个数组转换为 JSON 字符串

Python 中处理数组

import pandas as pd
from pandas

Frequently Asked Questions

How do I convert JSON to CSV in Python?

Use pandas: import pandas as pd; df = pd.json_normalize(data); df.to_csv("output.csv", index=False). For simple JSON, the csv module works too.

How do I handle nested JSON when converting to CSV?

Flatten nested objects using dot notation (e.g., address.city becomes a column). pandas json_normalize handles this automatically. For deeply nested data, write a recursive flattener.

What is the best command-line tool for JSON to CSV?

jq combined with csvkit: jq -r ".[] | [.field1, .field2] | @csv" input.json > output.csv. Miller (mlr) is also excellent for format conversion.

Can I convert CSV back to JSON?

Yes. In Python: pd.read_csv("file.csv").to_json(orient="records"). In JavaScript: use csv-parse or papaparse library.

How do I handle large JSON files that do not fit in memory?

Use streaming parsers like ijson (Python) or JSONStream (Node.js) to process records one at a time, writing CSV rows incrementally.

Related Tools