嵌套对象变成带点号的列
转换会一直往下走到每个叶子值,然后用它走过的路径给列命名。user 里 addr 里的 city,会变成一个叫 user.addr.city 的列。没有深度上限,所以列的数量取决于数据的形状,而不是顶层有几个键。
这部分行为很规矩,原理上也是可逆的:点号把你原本的结构记录了下来。
输入 JSON
[
{ "id": 1, "user": { "name": "Ada", "addr": { "city": "London" } } },
{ "id": 2, "user": { "name": "Alan", "addr": { "city": "Wilmslow" } } }
]输出 CSV
id,user.name,user.addr.city 1,Ada,London 2,Alan,Wilmslow
/json-to-csv/ 的真实输出。三个叶子值,三列,不管中间嵌了多少层。数组才是难处
数组的成员没有名字,只有位置,所以推导不出一个诚实的列名。转换器对两种情况的处理不一样,在你相信输出之前值得先知道这个分界。
全是普通值的数组会塌进一个格子,用分号加空格连起来:
输入 JSON
[
{ "id": 1, "tags": ["a", "b"] },
{ "id": 2, "tags": ["c"] }
]输出 CSV
id,tags 1,a; b 2,c
; 的话,现在跟两个标签已经分不出来了。对象数组会展开成带序号的列
数组里装的是对象时,每个位置拿到自己的一组列,按下标编号。这是忠实的 —— 没有任何合并 —— 但列的数量由整个数据集里最长的那一行决定。
看第二行:它只有一个 item,于是有四个格子是空的。一万条订单里只要有一条有 50 个明细,其余每一行都要背着 49 组空列。
输入 JSON
[
{ "id": 1, "items": [{ "sku": "x", "qty": 2 }, { "sku": "y", "qty": 1 }] },
{ "id": 2, "items": [{ "sku": "z", "qty": 5 }] }
]输出 CSV
id,items[0].sku,items[0].qty,items[1].sku,items[1].qty 1,x,2,y,1 2,z,5,,
缺失的键会对齐,不会串行
真实数据里的记录很少个个都带同样的键。表头是所有出现过的键的并集,按首次出现的顺序排;某条记录缺了哪个,就给它一个空格子,而不是让整行错位。
这是你想要的行为,也值得在任何转换器里都验一下:偷懒的实现会拿第一条记录的键当表头,然后不声不响地丢掉所有只在后面出现的字段。
输入 JSON
[
{ "id": 1, "name": "Ada" },
{ "id": 2, "email": "a@b.c" }
]输出 CSV
id,name,email 1,Ada, 2,,a@b.c
email 只出现在第二条记录里,一样拿到了列。什么都没丢。带外壳的数据,以及拆壳什么时候停下
接口返回通常会把行装在一个外壳里。当顶层对象恰好只有一个属性是数组时,那个数组就被当成行 —— 所以你可以把返回直接贴进来,不用先改形状。
一个数组:会拆壳
{ "items": [{ "a": 1 }, { "a": 2 }] }输出 CSV
a 1 2
两个数组就不猜了
有两个数组时,没办法判断哪个装的是行,于是转换器不再去猜,改成把整个对象当成一条记录来扁平化。结果是很宽的一行 —— 这几乎肯定不是你要的,而这恰恰是重点:错得明显,好过安静地对了一半。
所以看到一行带序号的列时,挑出你真正想要的那个数组,单独转它。
两个数组:不拆壳
{ "items": [{ "a": 1 }], "other": [{ "b": 2 }] }输出 CSV
items[0].a,other[0].b 1,2
items 就能拿到你想要的表格。引号,以及表格公式那个问题
格子里含有分隔符、引号或换行时会被加上引号 —— 就是标准的 CSV 规则,内部的引号写成两个。
还有一条值得知道:以 =、+、-、@ 开头的格子也会被加引号。这几个字符会让 Excel 和 Google Sheets 把格子当成公式,这就是一份 CSV 导出如何变成在别人机器上执行代码。加引号是这个问题里便宜的那一半解法。
输入 JSON
[{ "formula": "=1+1", "note": "a,b", "q": "say \"hi\"" }]输出 CSV
formula,note,q "=1+1","a,b","say ""hi"""
=1+1 因为开头的 = 被加引号,a,b 因为逗号,里面的引号写成了两个。有哪些是拿不回来的
转回去得到的是扁平的键,不是你原本的嵌套。点号作为普通字符留在键名里 —— 没有任何东西会把它们重新拼成对象,因为普通的 CSV 读取器无法知道 user.name 到底是个嵌套字段,还是一个名字里真的带点的列。
- 类型会在回来的路上被重新猜一遍。 CSV 格子里的
01234会解析成数字1234,跟不加引号的 YAML 一模一样。邮编和账号是常见的受害者。 - `null` 和空字符串变成同一个东西。 两者都是空格子,而空格子读回来是空字符串。
- 数组回不来。
a; b是一个含分号的字符串,不是列表。
输入 CSV
id,user.name 1,Ada
输出 JSON
[
{
"id": 1,
"user.name": "Ada"
}
]user.name 的扁平键,不是 user 对象。这是 /csv-to-json/ 的真实输出。实际建议
把 CSV 当导出格式,别当存储格式。目的地是表格软件、BI 工具或者一个人时,它是对的答案;打算之后再把数据当 JSON 读回来时,它是错的答案。
- 转你真正想要行的那个数组,别转它外面的壳。
- 如果记录里的数组长度不一致,把形状翻过来:一个数组元素一行,父级字段重复写。
- 相信一个文件之前先看表头 —— 它准确告诉你扁平化找到了哪些叶子。
- 留着原始 JSON。它是唯一还知道类型和结构的那份。
去试试,或者继续读
- JSON 转 CSV把嵌套对象扁平成点号列名,分隔符可选。
- CSV 转 JSON看看一个平文件读回来到底是什么。
- JSON 与 YAML同一个类型猜测问题,发生在人手写的那个格式里。
- JSON 完全指南语法、类型,以及那些报错背后的规则。