Home > Software engineering >  JSON.stringify not working with nested array of objects
JSON.stringify not working with nested array of objects

Time:01-17

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

CodePudding user response:

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)

CodePudding user response:

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();
}
  • Related