OpenCode 实践课:4 分钟用 Python 写出一个命令行 JSON 转 CSV 格式转换器

项目介绍

JSON 是 API 通信的事实标准,但数据分析师和 Excel 用户更习惯 CSV。今天我们用 OpenCode 花 4 分钟写一个命令行 JSON 转 CSV 转换器,支持嵌套 JSON 自动扁平化、从标准输入和文件读取、自定义分隔符,代码 100 行出头,只用 Python 标准库。

技术栈:Python 3 + argparse + json + csv

准备工作

  • Python 3.6 及以上
  • 安装 OpenCode(参考 opencode.ai 文档)
  • 一个终端

实践过程

第一步:搭建基础框架

我对 OpenCode 说:

> 用 Python 写一个命令行 JSON 转 CSV 工具,接收一个 JSON 文件路径作为参数,将 JSON 数组转为 CSV 输出到 stdout。用 argparse 解析参数。

OpenCode 秒级生成了核心代码:

import json
import csv
import sys
import argparse

def main():
    parser = argparse.ArgumentParser(description="JSON 转 CSV 转换器")
    parser.add_argument("file", help="JSON 文件路径")
    args = parser.parse_args()

    with open(args.file, "r", encoding="utf-8") as f:
        data = json.load(f)

    writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
    writer.writeheader()
    writer.writerows(data)

要点:OpenCode 自动选择了 csv.DictWriter——它比我手动拼逗号靠谱多了,还能正确处理引号转义。

第二步:支持 stdin 和嵌套扁平化

我对 OpenCode 说:

> 加上从 stdin 读取的功能,如果文件参数是 "-" 或不传,就从 stdin 读。另外 JSON 数组里如果有嵌套对象,自动扁平化——用点号连接嵌套键,比如 {"a": {"b": 1}} 变成列 "a.b"。

OpenCode 的响应:

def flatten(d, parent_key="", sep="."):
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(flatten(v, new_key, sep=sep).items())
        elif isinstance(v, list):
            items.append((new_key, json.dumps(v, ensure_ascii=False)))
        else:
            items.append((new_key, v))
    return dict(items)

要点:扁平化是这类工具的隐藏难点。直接告诉 OpenCode"用点号连接嵌套键",它就生成了递归展平函数。列表类型它自动序列化为 JSON 字符串——这是合理取舍。

第三步:处理列不均匀、加分隔符选项

我对 OpenCode 说:

> 现在如果数组里每个对象的键不一样,csv.DictWriter 会报错。需要先扫描所有行收集全部列名再输出。再加一个 -d 参数自定义分隔符,默认逗号。

OpenCode 修改后:

def collect_keys(data):
    keys = {}
    for row in data:
        flat = flatten(row)
        for k in flat:
            keys[k] = None
    return keys

要点:这个修改体现了"用自然语言描述边界条件"的思路。我不需要写具体的 key 收集逻辑,只需要描述问题:"如果每个对象键不一样会报错",OpenCode 就会补齐实现。

第四步:完整测试

我对 OpenCode 说:

> 帮我写一条测试命令,生成一份嵌套 JSON 并用管道传给脚本。

OpenCode 给出了测试命令,我跑了一下:

echo '[{"name":"张三","info":{"age":28,"city":"北京"},"tags":["go","python"]},{"name":"李四","info":{"age":32,"city":"上海"},"tags":["rust","js"]}]' | python json2csv.py - -d $'\t'

输出:

name	info.age	info.city	tags
张三	28	北京	["go", "python"]
李四	32	上海	["rust", "js"]

完整代码

#!/usr/bin/env python3
"""JSON 转 CSV 格式转换器 - 支持嵌套扁平化"""
import json
import csv
import sys
import argparse


def flatten(d, parent_key="", sep="."):
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(flatten(v, new_key, sep=sep).items())
        elif isinstance(v, list):
            items.append((new_key, json.dumps(v, ensure_ascii=False)))
        else:
            items.append((new_key, v))
    return dict(items)


def collect_keys(data):
    keys = {}
    for row in data:
        flat = flatten(row)
        for k in flat:
            keys[k] = None
    return keys


def main():
    parser = argparse.ArgumentParser(description="JSON 转 CSV 格式转换器")
    parser.add_argument("file", nargs="?", default="-", help="JSON 文件路径(默认从 stdin 读取)")
    parser.add_argument("-d", "--delimiter", default=",", help="CSV 分隔符(默认逗号)")
    args = parser.parse_args()

    if args.file == "-":
        data = json.load(sys.stdin)
    else:
        with open(args.file, "r", encoding="utf-8") as f:
            data = json.load(f)

    if isinstance(data, dict):
        data = [data]

    fieldnames = collect_keys(data)
    writer = csv.DictWriter(
        sys.stdout, fieldnames=fieldnames, delimiter=args.delimiter
    )
    writer.writeheader()
    for row in data:
        writer.writerow(flatten(row))


if __name__ == "__main__":
    main()

小结

从零到可用只花了 4 分钟。整段对话我只需要描述"我想要什么",不需要纠结 csv.DictWriter 的参数顺序、不需要手动处理嵌套字典的递归,也不需要写 key 收集的 boilerplate。

效率对比:如果手写,查文档、调试嵌套、处理边界情况至少 30 分钟。OpenCode 把"实现细节"完全交给了 AI,我只负责"我想要的效果是什么"——这才是 AI 编程助手的正确打开方式。