I have a CSV file which needs to be converted into a Javascript object / JSON file. Doesn't really matter which since I'll be be handling the data in JS anyway and either is fine.
So for instance this:
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 bee 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
}
}
]
This is what I have came up with:
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;
}
My code works, however there is an obvious flaw to it: for each layer it goes down into (e.g. house/address/street
), I have to manually write repeated if / else
statements.
Is there a better way to write it? I know this involves recursion or iteration of some kind but I just can't seem to figure out how.
I've searched around SO but most questions seem to be on doing it in Python instead of JS.
As far as possible I wish to have this done in vanilla JS without any other libraries.
I have a CSV file which needs to be converted into a Javascript object / JSON file. Doesn't really matter which since I'll be be handling the data in JS anyway and either is fine.
So for instance this:
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 bee 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
}
}
]
This is what I have came up with:
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;
}
My code works, however there is an obvious flaw to it: for each layer it goes down into (e.g. house/address/street
), I have to manually write repeated if / else
statements.
Is there a better way to write it? I know this involves recursion or iteration of some kind but I just can't seem to figure out how.
I've searched around SO but most questions seem to be on doing it in Python instead of JS.
As far as possible I wish to have this done in vanilla JS without any other libraries.
Share Improve this question asked May 25, 2017 at 10:36 rapinoporapinopo 331 silver badge5 bronze badges2 Answers
Reset to default 5You can achieve the intended results by creating the Object recursively.
Look at the code below:
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()
function basically constructs the resultant object recursively by looking at the column name, so if the column name contains the /
like in house/address/street
it will create a key in the object by the name house
and then recursively calls itself for the rest of the remaining keys in string i.e. address/street/
. The recursion ends when no more /
are left in the string and then it simply assigns the value in that key and returns the result object.
You can map over your records and create the objects on the fly :
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)