【问题标题】:Convert CSV to Nested JSON using / header delimiter使用 / 标头分隔符将 CSV 转换为嵌套 JSON
【发布时间】:2022-02-06 23:32:41
【问题描述】:

我的 CSV 标题看起来像

from/email to/0/email personalization/0/email/ personalization/0/data/first_name personalization/0/data/company_name personalization/0/data/job_title template_id

输出应该是:

[
 {
   "from": {
      "email": "me@x.com",
      "name": "Me"
   },
   "to": [
      {
         "email": "mike@x.com"
      }
   ],
   "personalization": [
      {
         "email": "mike@x.com",
         "data": {
            "first_name": "Mike",
            "company_name": "X.com",
            "job_title": "Chef"
         }
      }
   ],
   "template_id": "123456"
},

我试过了

csvjson input.csv output.csv
csvtojson input.csv output.csv
csv2json input.csv output.csv
python3 app.py

import csv 
import json 

def csv_to_json(csvFilePath, jsonFilePath):
    jsonArray = []
      
    #read csv file
    with open(csvFilePath, encoding='utf-8') as csvf: 
        #load csv file data using csv library's dictionary reader
        csvReader = csv.DictReader(csvf) 

        #convert each csv row into python dict
        for row in csvReader: 
            #add this python dict to json array
            jsonArray.append(row)
  
    #convert python jsonArray to JSON String and write to file
    with open(jsonFilePath, 'w', encoding='utf-8') as jsonf: 
        jsonString = json.dumps(jsonArray, indent=4)
        jsonf.write(jsonString)
          
csvFilePath = r'outputt1.csv'
jsonFilePath = r'outputt1.json'
csv_to_json(csvFilePath, jsonFilePath)
node app.js

const CSVToJSON = require('csvtojson');

// convert users.csv file to JSON array
CSVToJSON().fromFile('outputt1.csv')
    .then(from => {

        // from is a JSON array
        // log the JSON array
        console.log(from);
    }).catch(err => {
        // log error if any
        console.log(err);
    });

所有输出都是单行 JSON 的一些变体,没有嵌套。

唯一可行的方法是将其上传到https://www.convertcsv.com/csv-to-json.htm 并手动转换每个文件,但这显然不是解决方案。

我看到一篇推荐 Choetl.Json 用于此目的的帖子,但无法在 mac 上安装它

【问题讨论】:

  • 到目前为止你有什么尝试?
  • 所以不是dial-a-coder
  • 我的坏人请看编辑
  • 显示表头很好,但需要提供1或2行数据。我们这些想帮助你的人,没有什么可测试的。
  • .from.name 从何而来?这不是标题

标签: python node.js json bash jq


【解决方案1】:

您的问题应该分为两部分:解析 CSV 数据以转换为 JSON,以及按照类似路径的描述符构建 JSON 结构。

对于第一部分,有必要澄清 CSV 输入的格式,因为 CSV 没有通用标准,只有 RFC 4180 proposal 中的基本描述以及针对特定用例或数据量身定制的大量采用类型。由于您没有提供任何实际的 CSV 内容,为了简单起见,我们假设记录由换行符分隔,字段由逗号分隔,并且不存在字段分隔符,因为数据本身不包含任何这些分隔符。让我们进一步假设所有记录具有完全相同数量的字段,并且其中一个(即第一个)代表标题。您可能需要将这些假设调整为您的实际 CSV 数据。

cat input.csv
from/email,to/0/email,personalization/0/email,personalization/0/data/first_name,personalization/0/data/company_name,personalization/0/data/job_title,template_id
me@x.com,mike@x.com,mike@x.com,Mike,X.com,Chef,123456

基于此格式,您可以使用 --raw-input-R 选项读取 CSV 数据,该选项在每个以换行符分隔的原始文本段中作为 JSON 字符串输入流式传输。理想情况下,您的过滤器应通过在逗号处拆分将每个输入字符串记录转换为字符串字段数组,例如使用/ 运算符:

jq -R '. / ","' input.csv
[
  "from/email",
  "to/0/email",
  "personalization/0/email",
  "personalization/0/data/first_name",
  "personalization/0/data/company_name",
  "personalization/0/data/job_title",
  "template_id"
]
[
  "me@x.com",
  "mike@x.com",
  "mike@x.com",
  "Mike",
  "X.com",
  "Chef",
  "123456"
]

Demo

至于第二部分,您现在可以轻松处理这些 JSON 数组。为了分别处理第一个(标题),您可以使用--slurp-s 选项将输入流转换为数组,然后可以使用索引访问其元素。此外,setpath 内置函数也派上用场,因为它可以在 JSON 结构中设置值,该结构描述为表示对象字段和数组索引的字符串和整数数组,就像您在标题中所做的那样。这使您可以通过在“/”处拆分并将类似数字的段转换为实际数字来将标题字符串转换为此类数组。最后,要连续构建您的 JSON 对象,您可以使用 reduce 语句遍历记录字段,并使用 transpose 将记录字段与其对应的标头字段对齐:

… | jq -s '
  (.[0] | map(. / "/" | map(tonumber? // .))) as $headers
  | .[1:] | map(
    reduce ([$headers, .] | transpose[]) as [$path, $value] (
      {}; setpath($path; $value)
    )
  )
'
[
  {
    "from": {
      "email": "me@x.com"
    },
    "to": [
      {
        "email": "mike@x.com"
      }
    ],
    "personalization": [
      {
        "email": "mike@x.com",
        "data": {
          "first_name": "Mike",
          "company_name": "X.com",
          "job_title": "Chef"
        }
      }
    ],
    "template_id": "123456"
  }
]

Demo

注意事项

  • 我的展示忽略了这样一个事实,即您的示例 JSON 输出还在顶级字段 from 下提供了一个附加字段 name,因为您的示例 CSV 输入标头不包含匹配字段 from/name
  • 为了强调这种方法的二分性,我以 jq 的两个级联调用结束。这通常可以(并且大部分应该)合并为一个。但是,由于组合选项 --raw-input--slurp 会改变 jq 的读入行为,您宁愿在第一个过滤器中添加带有 [inputs | …]--null-input-n 选项,这样您就可以关闭--slurp 第二个选项:jq -Rn '[inputs / "/"] | …' (Demo)

【讨论】:

  • 谢谢 pmf 对不起,如果我是哑巴,但是什么... |什么意思?
  • "…" 应该代表之前对 jq 的调用。如果您保留两次调用(请参阅末尾的第二个注释),您将进行第一次调用并将其输出通过管道传输到第二次:jq -R '. / ","' input.csv | jq -s '…'(这里,“...”代表第二次调用的主体,它是太长,无法在此处复制)。但请考虑应用上述注释将它们合并为一个。
  • 嗨 pmf 我试过了,输出是jq: error (at <stdin>:803): Path must be specified as an array
  • 您没有提供任何关于为什么会发生这种情况的线索。牢记 glenn jackman 的 comment 并“提供 1 或 2 行数据”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 2021-08-26
  • 2018-01-07
  • 2020-04-02
  • 2020-10-28
相关资源
最近更新 更多