使用Javascript将CSV转换为嵌套的JSON

3

我将要把一个CSV文件转换成Javascript对象/JSON文件。不管是哪种格式转换,因为最终我会在JS中处理数据,所以都可以。

例如,这个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

should become this:

[
    {
        "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的。尽可能地,我希望这可以在原生JS中完成,而不需要任何其他库。
2个回答

5
您可以通过递归创建对象来实现预期的结果。请看下面的代码:

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
@rapinopo .splice() 返回一个被删除元素的数组。因此 attrs 将会是一个数组。 - abhishekkannojia
@abhishekkannojia - 这个递归函数显然只创建JSON对象。例如,我期望house/0/type通过名称为house的JSON数组创建第一个JSON对象,其中包含一个名为type的键。但它并没有按照这种方式工作。 - Sankararaman Santhanaraman

-1
你可以遍历你的记录并即时创建对象:

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)


1
这个也可以工作,但并没有解决问题;它仍然需要手动列出每个键。 - rapinopo
是的,你只需要执行一次这个操作,如果你有一个包含500行的CSV文件,它就会创建一个包含500个对象的数组。问题出在哪里呢? - Jeremy Thille

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接