【问题标题】:Converting CSV to nested JSON in Javascript在 Javascript 中将 CSV 转换为嵌套的 JSON
【发布时间】:2017-10-25 23:28:20
【问题描述】:

我有一个 CSV 文件,需要转换为 Javascript 对象/JSON 文件。哪个并不重要,因为无论如何我都会在 JS 中处理数据,而且两者都可以。

比如这个:

name,birthday/day,birthday/month,birthday/year,house/type,house/address/street,house/address/city,house/address/state,house/occupants
Lily Haywood,27,3,1995,Igloo,768 Pocket Walk,Honolulu,HI,7
Stan Marsh,19,10,1987,Treehouse,2001 Bonanza Street,South Park,CO,2

应该变成这样:

[
    {
        "name": "Lily Haywood",
        "birthday": {
            "day": 27,
            "month": 3,
            "year": 1995
        },
        "house": {
            "type": "Igloo",
            "address": {
                "street": "768 Pocket Walk",
                "city": "Honolulu",
                "state": "HI"
            },
            "occupants": 7
        }
    },
    {
        "name": "Stan Marsh",
        "birthday": {
            "day": 19,
            "month": 10,
            "year": 1987
        },
        "house": {
            "type": "Treehouse",
            "address": {
                "street": "2001 Bonanza Street",
                "city": "South Park",
                "state": "CO"
            },
            "occupants": 2
        }
    }
]

这是我想出的:

function parse(csv){
    function createEntry(header){
        return function (record){
            let keys = header.split(",");
            let values = record.split(",");
            if (values.length !== keys.length){
                console.error("Invalid CSV file");
                return;
            }
            for (let i=0; i<keys.length; i++){
                let key = keys[i].split("/");
                let value = values[i] || null;
                /////
                if (key.length === 1){
                    this[key] = value;
                }
                else {
                    let newKey = key.shift();
                    this[newKey] = this[newKey] || {};
                    //this[newKey][key[0]] = value;
                    if (key.length === 1){
                        this[newKey][key[0]] = value;
                    }
                    else {
                        let newKey2 = key.shift();
                        this[newKey][newKey2] = this[newKey][newKey2] || {};
                        this[newKey][newKey2][key[0]] = value;
                        //if (key.length === 1){}
                        //...
                    }
                }
                /////
                }
        };
    }
    let lines = csv.split("\n");
    let Entry = createEntry(lines.shift());
    let output = [];
    for (let line of lines){
        entry = new Entry(line);
        output.push(entry);
    }
    return output;
}

我的代码可以工作,但是它有一个明显的缺陷:对于它进入的每一层(例如house/address/street),我必须手动编写重复的if / else 语句。

有没有更好的写法?我知道这涉及某种递归或迭代,但我似乎无法弄清楚如何。

我已经搜索过 SO,但大多数问题似乎都是在 Python 而不是 JS 中进行的。

我希望尽可能在没有任何其他库的情况下在 vanilla JS 中完成这项工作。

【问题讨论】:

    标签: javascript json csv


    【解决方案1】:

    您可以通过递归创建对象来达到预期的效果。
    看下面的代码:

    var csv = [
      "name,birthday/day,birthday/month,birthday/year,house/type,house/address/street,house/address/city,house/address/state,house/occupants",
      "Lily Haywood,27,3,1995,Igloo,768 Pocket Walk,Honolulu,HI,7",
      "Stan Marsh,19,10,1987,Treehouse,2001 Bonanza Street,South Park,CO,2"
    ];
    
    var attrs = csv.splice(0,1);
    
    var result = csv.map(function(row) {
      var obj = {};
      var rowData = row.split(',');
      attrs[0].split(',').forEach(function(val, idx) {
        obj = constructObj(val, obj, rowData[idx]);
      });
      return obj;
    })
    
    
    function constructObj(str, parentObj, data) {
      if(str.split('/').length === 1) {
        parentObj[str] = data;
        return parentObj;
      }
    
      var curKey = str.split('/')[0];
      if(!parentObj[curKey])
        parentObj[curKey] = {};
      parentObj[curKey] = constructObj(str.split('/').slice(1).join('/'), parentObj[curKey], data);
      return parentObj;
    }
    
    console.log(result);
    .as-console-wrapper{max-height: 100% !important; top:0}

    constructObj() 函数基本上通过查看列名递归地构造结果对象,因此如果列名包含 / 就像在 house/address/street 中一样,它将在对象中创建一个名为 house 的键,并且然后递归调用自己以获取字符串中剩余的键,即address/street/。当字符串中没有更多的/ 时递归结束,然后它简单地分配该键中的值并返回结果对象。

    【讨论】:

    • 谢谢,这正是我想要的!有点跑题了,为什么是attrs[0].split(',') 而不是attrs.split(',')attrs 不就是 "name,birthday/day,..." 吗?
    • @rapinopo .splice() 返回一个已删除元素的数组。因此attrs 将是一个数组。
    • @abhishekkannojia - 这个递归函数显然只创建 JSON 对象。例如,我希望 house/0/type 创建一个名为 house 的 JSON 数组,其中的第一个 JSON 对象包含一个名为 type 的键。它不是那样工作的。
    【解决方案2】:

    您可以映射您的记录并动态创建对象:

    let records = ['Lily Haywood,27,3,1995,Igloo,768 Pocket Walk,Honolulu,HI,7',
    'Stan Marsh,19,10,1987,Treehouse,2001 Bonanza Street,South Park,CO,2']
    
    let output = records.map( record => {
    	
    	let arr = record.split(',')
    	
    	return {
    		 "name": arr[0],
            "birthday": {
                "day": parseInt(arr[1]),
                "month": parseInt(arr[2]),
                "year": parseInt(arr[3])
            },
            "house": {
                "type": arr[4],
                "address": {
                    "street": arr[5],
                    "city": arr[6],
                    "state": arr[7]
                },
                "occupants": parseInt(arr[8])
            }
    		
    	}
    })
    
    console.log(output)

    【讨论】:

    • 这也可以,但不能解决问题;它仍然需要手动列出每个键。
    • 嗯,是的,你这样做一次,然后如果你有一个 500 行的 CSV,它会创建一个包含 500 个对象的数组。问题出在哪里?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    • 2020-10-28
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多