3

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 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
        }
    }
]

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.

2 Answers 2

5

You 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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you, this is exactly what I was looking for! Slightly off-topic, why attrs[0].split(',') instead of attrs.split(',')? Isn't attrs just "name,birthday/day,..."?
@rapinopo .splice() returns an array of deleted elements. Therefore attrs will be an array.
@abhishekkannojia - this recursive function apparently creates only JSON objects. For instance, I expect house/0/type to create a JSON array by name house with the first JSON Object inside that containing a key called type. It is not working that way.
-1

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)

2 Comments

This works as well but doesn't solve the problem; it still requires manually listing out each key.
Well yes, you do this once, and then if you have a CSV with 500 lines, it creates an array of 500 objects. Where's the problem?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.