0

I know this question has been asked but none of the solutions are working for me and I can't figure out what's wrong. I have an object with a nested array of objects I am stringifying but I get a blank array of the nested array when I use JSON.stringify on it.

This is a simplified version of the way I'm constructing the object. The main difference is that there is a for loop iterating through all the rows, here I am just manually creating 2 rows

// JAVASCRIPT
let obj = {};
obj['type'] = 'Setting';
obj['id'] = 1;
obj['import'] = parseCSV();

function parseCSV() {
    let jsonData = [];
    let row1 = {};
    let row2 = {};
    row1['date'] = '2022-01-01';
    row1['amount'] = '30';
    row2['date'] = '2022-01-02';
    row2['amount'] = '50';
    jsonData.push(row1);
    jsonData.push(row2);
    return jsonData;
}

console.log('RAW DATA', obj);
console.log('STRINGIFIED', JSON.stringify(obj));

The above outputs the correct stringified JSON

Stringified Working

But the full version of my code gives me a blank array for import.

Full Object

Stringified Not Working

Both objects look identical to me. The culprit is somewhere in my parseCSV function, because when I use the simplified version above I get the correct stringified data, but I can't pinpoint where I'm wrong. Below is my full function.

function parseCSV(file) {
    let filename = file.name;
    let extension = filename.substring(filename.lastIndexOf('.')).toUpperCase();
    if(extension == '.CSV') {
        try {
            let reader = new FileReader();
            let jsonData = [];
            let headers = [];
            reader.readAsBinaryString(file);
            reader.onload = function(e) {
                let rows = e.target.result.split('\n');
                for(let i = 0; i < rows.length; i++) {
                    let cells = rows[i].split(',');
                    let rowData = {};
                    for(let j = 0; j < cells.length; j++) {
                        if(i == 0) headers.push(cells[j].trim());
                        else {
                            if(headers[j]) rowData[headers[j]] = cells[j].trim();
                        }
                    }
                    if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
                }
            }
            return jsonData;
        } catch(err) {
            console.error('!! ERROR READING CSV FILE', err);
        }
    } else alert('PLEASE UPLOAD A VALID CSV FILE');*/
}

Thanks for the help!

EDIT

When I add await before parseCSV as @BJRINT's answer suggests I get a syntax error await is only valid in async function

async function submitForm(event) {
    event.preventDefault();
    
    let newItem = await gatherFormData(event.target);
    
    return fetch('server.php', {
        method: "POST",
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify(newItem)
    })
    .then(checkError)
    .then(data => parseData(data))
    .catch(err => console.error('>> ERROR READING JSON DATA', err));
}

function gatherFormData(target) {
    const inputs = target.querySelectorAll('input');
    let obj = {};
    inputs.forEach(function(input) {
        if(intKeys.indexOf(input.name) >= 0) obj[input.name] = parseInt(input.value);
        else if(curKeys.indexOf(input.name) >= 0) obj[input.name] = parseInt(parseFloat(input.value) * 100);
        else if(chkKeys.indexOf(input.name) >= 0) input.checked ? obj[input.name] = 1 : obj[input.name] = 0;
        else if(fileKeys.indexOf(input.name) >= 0 && input.files.length > 0) obj[input.name] = parseCSV(input.files[0]);
        else obj[input.name] = input.value;
    });
    return obj;
}

oscarale
  • 1
  • 2
  • You're returning the data before it can be filled with the CSV data, so you're just stringifying an empty array. The only reason why it "looks" like it's right in the console is because Chrome's console is live and updates its logs if the object was updated. – kelsny Jan 13 '23 at 19:51
  • Thanks, you're right. I am unsure though of how to make sure jsonData gets updated properly and returned – oscarale Jan 13 '23 at 20:35

2 Answers2

0

The problem does not come from the stringify function. Since you are filling your array asynchronously (when the reader callback is executed) but returning your data first, it is empty.

You could wrap your function with a Promise that resolves when the reader callback function is finally executed, like so:

function parseCSV(file) {
    return new Promise((resolve, reject) => {
        let filename = file.name;
        let extension = filename.substring(filename.lastIndexOf('.')).toUpperCase();
        if(extension !== '.CSV') 
            return reject('PLEASE UPLOAD A VALID CSV FILE')

        try {
            let reader = new FileReader();
            let jsonData = [];
            let headers = [];
            reader.readAsBinaryString(file);
            reader.onload = function(e) {
                let rows = e.target.result.split('\n');
                for(let i = 0; i < rows.length; i++) {
                    let cells = rows[i].split(',');
                    let rowData = {};
                    for(let j = 0; j < cells.length; j++) {
                        if(i == 0) headers.push(cells[j].trim());
                        else {
                            if(headers[j]) rowData[headers[j]] = cells[j].trim();
                        }
                    }
                    if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
                }
                return resolve(jsonData);
            }
            
        } catch(err) {
            return reject('!! ERROR READING CSV FILE', err);
        }
    })
}


// calling the function
const data = await parseCSV(file)
BJRINT
  • 639
  • 3
  • 8
  • Thanks for the help. But now I'm getting an `await is only valid in async functions, async generators and modules` syntax error – oscarale Jan 13 '23 at 20:14
  • This is still incorrect... you are resolving the promise before the data has been pushed. You should only resolve inside the `onload` callback, after you have pushed the data. – kelsny Jan 13 '23 at 20:39
  • @vera edited, I typed a bit too fast. – BJRINT Jan 13 '23 at 20:45
  • Thanks everyone. I figured out the `await` syntax error but I'm still getting a blank array for the `import` value of the stringified json with your solution. I've included my other two functions in my original question. – oscarale Jan 13 '23 at 21:01
0

The solution that worked for my specific case was to use a combination of BJRINT's answer and a timer to keep checking if the data had finished loading which I found here.

async function parseCSV(file) {
    return await new Promise((resolve, reject) => {
        let extension = file.name.substring(file.name.lastIndexOf('.')).toUpperCase();
        if(extension !== '.CSV') reject('PLEASE UPLOAD A VALID CSV FILE');
        
        try {
            let reader = new FileReader();
            reader.readAsText(file);
            reader.onload = function(e) {
                let jsonData = [];
                let headers = [];
                let rows = e.target.result.split(/\r\n|\r|\n/);
                for(let i = 0; i < rows.length; i++) {
                    let cells = rows[i].split(',');
                    let rowData = {};
                    for(let j = 0; j < cells.length; j++) {
                        if(i == 0) headers.push(cells[j].trim());
                        else {
                            if(headers[j]) rowData[headers[j]] = cells[j].trim();
                        }
                    }
                    if(i != 0 && rowData['date'] != '') jsonData.push(rowData);
                }
                resolve(jsonData);
            }
        } catch(err) {
            reject(err);
        }
    });
}

function submitForm(event) {
    event.preventDefault();
    showForm(false);
    loading.classList.remove('hidden');
    
    let ready = true;
    const inputs = event.target.querySelectorAll('input');
    let newItem = {};
    
    let check = function() {
        if(ready === true) {
            console.log(newItem);
            console.log(JSON.stringify(newItem));
            
            return fetch('server.php', {
                method: "POST",
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                body: JSON.stringify(newItem)
            })
            .then(checkError)
            .then(data => parseData(data))
            .catch(err => console.error('>> ERROR READING JSON DATA', err));
        }
        setTimeout(check, 1000);
    }
    
    inputs.forEach(function(input) {
        if(intKeys.indexOf(input.name) >= 0) newItem[input.name] = parseInt(input.value);
        else if(curKeys.indexOf(input.name) >= 0) newItem[input.name] = parseInt(parseFloat(input.value) * 100);
        else if(chkKeys.indexOf(input.name) >= 0) input.checked ? newItem[input.name] = 1 : newItem[input.name] = 0;
        else if(fileKeys.indexOf(input.name) >= 0 && input.files.length > 0) {
            ready = false;
            parseCSV(input.files[0]).then(data => {
                ready = true;
                newItem[input.name] = data;
            });
        }
        else newItem[input.name] = input.value;
    });
    
    check();
}
oscarale
  • 1
  • 2