Home > Software engineering >  How to convert multiple arrays to CSV columns using JavaScript?
How to convert multiple arrays to CSV columns using JavaScript?

Time:10-23

Goal: Convert multiple JavaScript arrays to CSV columns.

Context: Using an API to scan through a fleet of assets with GPS units and comprising a list of which assets are inactive, among other metrics.

Problem: The part I need assistance with is the algorithm in my nested for-loop that determines the index to insert the cell at the correct row/column.

I have looked at many packages on NPM but it doesn't appear that I was able to easily convert multiple arrays to CSV columns.

I haven't needed to ask a question here yet so it's certainly possibly I am going about this the wrong way or missed a solution to this; so any help or hints to achieve the goal are highly appreciated.

The running code is below, but the desired output would be:

All Assets,Idle Assets
1000,2001,
1001,2002,
1002,2003,
1003,2004,
1004,,

Rather than:

All Assets,Idle Assets
1000,2001,
2002,
2003,
2004,
,
1001,1002,1003,1004,

//Sample Array Data (can handle more arrays/columns)
const allAssets = ['1000', '1001', '1002', '1003', '1004'];
const idleAssets = ['2001', '2002', '2003', '2004'];

////Arrays to CSV columns
const headers = ['All Assets', 'Idle Assets'];
const columns = [allAssets, idleAssets];

//Identify columnLen (# of arrays) & rowLen (length of longest array)
let longestArr = 0;
columns.forEach((arr) =>
    arr.length > longestArr
        ? longestArr = arr.length
        : undefined);

const rowLen = longestArr;
const columnLen = columns.length;
const heading = (headers.join(',')).concat('\n');

const csvConstructor = () => {
    const data = [];
    for (let column = 0; column < columnLen; column  ) {
        const columnArray = columns[column];
        for (let row = 0; row < rowLen; row  ) {
            let cell;
            
            //Account for different sized arrays
            columnArray[row] === undefined
                ? cell = ','
                : cell = columnArray[row].concat(',');

            //New line for cells in last column
            (columns.length - 1) === column
                ? cell = cell.concat('\n')
                : undefined;

            //Dynamically insert cell into appropriate data index
            const insertAt = column   row; //<--- Need help here
            data.splice(insertAt, 0, cell);
        }
    }
    const result = heading.concat(data.join(''));
    return result
}

const csv = csvConstructor();
console.log(csv);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Your csvConstructor() is iterating columns individually, but for a table, you need to iterate columns in parallel. Also, it is trying to juggle two different concerns at the same time (transforming the data, and constructing the csv string), so it is a bit difficult to follow.

The problem is logically split into two phases. In the first phase, you want a well-formatted array that reflects the CSV's tabular structure (or if it's a large data set, then you probably want an iterator that yields rows one by one, but I won't do that version here).

The output should be in this format:

const result = [
  [header A, header B],
  [value A, value B],
  [value A, value B],
  // ...etc...
]

Once we have that structure, we can transform it into CSV.

So to get that based on your data:

function toCsvRows(headers, columns) {
  const output = [headers]
  const numRows = columns.map(col => col.length)
    .reduce((a, b) => Math.max(a, b))

  for (let row = 0; row < numRows; row  ) {
    output.push(columns.map(c => c[row] || ''))
  }

  return output
}

function toCsvString(data) {
  let output = ''
  data.forEach(row => output  = row.join(',')   '\n')
  return output
}

function csvConstructor(headers, columns) {
  return toCsvString(toCsvRows(headers, columns))
}

Here's a working fiddle:

https://jsfiddle.net/foxbunny/fdxp3bL5/

EDIT: Actually, let me do the memory-efficient version as well:

function *toCsvRows(headers, columns) {
  yield headers

  const numRows = columns.map(col => col.length)
    .reduce((a, b) => Math.max(a, b))

  for (let row = 0; row < numRows; row  ) {
    yield columns.map(c => c[row] || '')
  }
}

function toCsvString(data) {
  let output = ''
  for (let row of data) {
    output  = row.join(',')   '\n'
  }
  return output
}

function csvConstructor(headers, columns) {
  return toCsvString(toCsvRows(headers, columns))
}

CodePudding user response:

If you want a simple way using zip two arrays with comma. then join header and zipped rows with \n.

const   allAssets  = ['1000', '1001', '1002', '1003', '1004'],
        idleAssets = ['2001', '2002', '2003', '2004'],
        headers    = ['All Assets', 'Idle Assets'],
        zip        = (a, b) => a.map((k, i) => `${k}, ${b[i]||''}`),
        zipLonger = (a, b) => a.length > b.length ? zip(a, b) : zip(b, a);
console.log(headers.join(', ') '\n' 
           zipLonger(allAssets, idleAssets).join('\n'))

Result:

All Assets, Idle Assets
1000, 2001
1001, 2002
1002, 2003
1003, 2004
1004, 
  • Related