├── .babelrc ├── .npmignore ├── src ├── helpers │ ├── value-or-empty.js │ ├── check-separator.js │ ├── check-if-valid.js │ ├── check-special-chars-and-empty.js │ └── append-element.js ├── modules │ ├── convert-array-of-arrays-to-csv.js │ └── convert-array-of-objects-to-csv.js └── index.js ├── .eslintrc ├── .gitignore ├── .travis.yml ├── test ├── helpers │ ├── check-if-valid.spec.js │ ├── check-separator.spec.js │ └── check-special-chars-and-empty.spec.js ├── fixtures │ ├── options.js │ ├── expected-results.js │ └── data.js ├── index.spec.js └── modules │ ├── convert-array-of-objects-to-csv.spec.js │ └── convert-array-of-arrays-to-csv.spec.js ├── .vscode └── launch.json ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | test 3 | .babelrc 4 | .eslintrc 5 | .gitignore 6 | .travis.yml 7 | yarn.lock 8 | package-lock.json 9 | src 10 | -------------------------------------------------------------------------------- /src/helpers/value-or-empty.js: -------------------------------------------------------------------------------- 1 | export const valueOrEmpty = (data) => { 2 | if (data || data === false || data === 0) { 3 | return data; 4 | } 5 | 6 | return ''; 7 | }; 8 | -------------------------------------------------------------------------------- /src/helpers/check-separator.js: -------------------------------------------------------------------------------- 1 | export const checkSeparator = (separator) => { 2 | if (!/^[^\n"]$/.test(separator)) { 3 | throw new Error('The separator must be single-character and cannot be a newline or double quotes'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "import/prefer-default-export": 0, 5 | "arrow-parens": [ 6 | "error", 7 | "always" 8 | ] 9 | }, 10 | "env": { 11 | "jest": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # modules 2 | node_modules 3 | npm-debug.log 4 | 5 | # generated 6 | coverage 7 | .nyc* 8 | *.log 9 | *.cache 10 | .eslintcache 11 | yarn.lock 12 | 13 | # macOS 14 | .DS_STORE 15 | 16 | # IDE / Editors 17 | .idea/ 18 | 19 | # babel transformed 20 | lib 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: true 3 | dist: trusty 4 | node_js: 5 | - 12 6 | - 14 7 | install: 8 | - npm i 9 | - npm i -g codecov 10 | script: 11 | - npm test 12 | notifications: 13 | email: 14 | on_failure: change 15 | after_success: 16 | - codecov 17 | -------------------------------------------------------------------------------- /src/helpers/check-if-valid.js: -------------------------------------------------------------------------------- 1 | export const checkIfValid = (data) => { 2 | if (!Array.isArray(data)) { 3 | throw new Error(`data has to be typeof: ${typeof []} and instanceof Array: ${[] instanceof Array} but got typeof: ${typeof data} and instanceof Array: ${data instanceof Array}`); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/helpers/check-if-valid.spec.js: -------------------------------------------------------------------------------- 1 | import { checkIfValid } from '../../src/helpers/check-if-valid'; 2 | 3 | test('checkIfValid | wrong data', () => { 4 | const result = () => checkIfValid({}); 5 | 6 | expect(result).toThrow('data has to be typeof: object and instanceof Array: true but got typeof: object and instanceof Array: false'); 7 | }); 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | // macOS compatible: 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Jest Current Test", 12 | "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", 13 | "args": [ 14 | "${file}" 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/helpers/check-special-chars-and-empty.js: -------------------------------------------------------------------------------- 1 | const SYMBOLS = '\\n"'; 2 | 3 | export const checkSpecialCharsAndEmpty = (value, separator = null) => { 4 | const thisValue = value.toString().toLowerCase(); 5 | let hasSpecialChars = false; 6 | if (typeof value === 'string') { 7 | let regexp = SYMBOLS; 8 | if (typeof separator !== 'undefined' && separator !== null && !SYMBOLS.includes(separator)) { 9 | regexp += separator; 10 | } 11 | hasSpecialChars = thisValue.length === 0 || new RegExp(`[${regexp}]`).test(thisValue); 12 | } 13 | 14 | return hasSpecialChars; 15 | }; 16 | -------------------------------------------------------------------------------- /src/modules/convert-array-of-arrays-to-csv.js: -------------------------------------------------------------------------------- 1 | import { appendElement } from '../helpers/append-element'; 2 | import { valueOrEmpty } from '../helpers/value-or-empty'; 3 | 4 | export const convertArrayOfArraysToCSV = (data, { header, separator }) => { 5 | const array = [...data]; 6 | let csv = ''; 7 | 8 | if (header) { 9 | header.forEach((headerEl, i) => { 10 | const thisHeaderEl = valueOrEmpty(headerEl); 11 | 12 | csv += appendElement(thisHeaderEl, header.length, i, separator); 13 | }); 14 | } 15 | 16 | array.forEach((row) => { 17 | row.forEach((value, i) => { 18 | const thisValue = valueOrEmpty(value); 19 | 20 | csv += appendElement(thisValue, row.length, i, separator); 21 | }); 22 | }); 23 | 24 | return csv; 25 | }; 26 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { checkIfValid } from './helpers/check-if-valid'; 2 | import { checkSeparator } from './helpers/check-separator'; 3 | import { convertArrayOfArraysToCSV } from './modules/convert-array-of-arrays-to-csv'; 4 | import { convertArrayOfObjectsToCSV } from './modules/convert-array-of-objects-to-csv'; 5 | 6 | export const convertArrayToCSV = (data, { header, separator } = {}) => { 7 | checkIfValid(data); 8 | 9 | const thisOptions = { 10 | header, 11 | separator: separator || ',', 12 | }; 13 | 14 | checkSeparator(thisOptions.separator); 15 | 16 | if (Array.isArray(data[0])) { 17 | return convertArrayOfArraysToCSV(data, thisOptions); 18 | } 19 | 20 | return convertArrayOfObjectsToCSV(data, thisOptions); 21 | }; 22 | 23 | export default convertArrayToCSV; 24 | -------------------------------------------------------------------------------- /src/helpers/append-element.js: -------------------------------------------------------------------------------- 1 | import { checkSpecialCharsAndEmpty } from './check-special-chars-and-empty'; 2 | 3 | const separatorOrLineBreak = (length, elementIdx, separator) => ( 4 | length - 1 === elementIdx ? '\n' : separator 5 | ); 6 | 7 | const escapeDoubleQuotesInsideElement = (element) => element.replace(/"/g, '""'); 8 | 9 | const appendElement = (element, lineLength, elementIdx, separator) => { 10 | const includesSpecials = checkSpecialCharsAndEmpty(element, separator); 11 | 12 | let thisElement = element; 13 | 14 | if (includesSpecials) { 15 | thisElement = escapeDoubleQuotesInsideElement(thisElement); 16 | } 17 | const afterElement = separatorOrLineBreak(lineLength, elementIdx, separator); 18 | 19 | return ( 20 | includesSpecials ? `"${thisElement}"${afterElement}` : `${thisElement}${afterElement}` 21 | ); 22 | }; 23 | 24 | export { appendElement }; 25 | -------------------------------------------------------------------------------- /test/helpers/check-separator.spec.js: -------------------------------------------------------------------------------- 1 | import { checkSeparator } from '../../src/helpers/check-separator'; 2 | 3 | const ERROR_MESSAGE = 'The separator must be single-character and cannot be a newline or double quotes'; 4 | 5 | test('checkSeparator | wrong separator | newline', () => { 6 | const result = () => checkSeparator('\n'); 7 | 8 | expect(result).toThrow(ERROR_MESSAGE); 9 | }); 10 | 11 | test('checkSeparator | wrong separator | double quotes', () => { 12 | const result = () => checkSeparator('"'); 13 | 14 | expect(result).toThrow(ERROR_MESSAGE); 15 | }); 16 | 17 | test('checkSeparator | wrong separator | two symbols', () => { 18 | const result = () => checkSeparator('ab'); 19 | 20 | expect(result).toThrow(ERROR_MESSAGE); 21 | }); 22 | 23 | test('checkSeparator | supported separator', () => { 24 | const result = () => checkSeparator('|'); 25 | 26 | expect(result).not.toThrow(ERROR_MESSAGE); 27 | }); 28 | -------------------------------------------------------------------------------- /src/modules/convert-array-of-objects-to-csv.js: -------------------------------------------------------------------------------- 1 | import { appendElement } from '../helpers/append-element'; 2 | import { valueOrEmpty } from '../helpers/value-or-empty'; 3 | 4 | export const convertArrayOfObjectsToCSV = (data, { header, separator }) => { 5 | const array = [...data]; 6 | let csv = ''; 7 | 8 | if (header) { 9 | header.forEach((headerEl, i) => { 10 | const thisHeaderEl = valueOrEmpty(headerEl); 11 | 12 | csv += appendElement(thisHeaderEl, header.length, i, separator); 13 | }); 14 | } 15 | 16 | array.forEach((row, idx) => { 17 | const thisRow = Object.keys(row); 18 | if (!header && idx === 0) { 19 | thisRow.forEach((key, i) => { 20 | const value = valueOrEmpty(key); 21 | 22 | csv += appendElement(value, thisRow.length, i, separator); 23 | }); 24 | } 25 | 26 | thisRow.forEach((key, i) => { 27 | const value = valueOrEmpty(row[key]); 28 | 29 | csv += appendElement(value, thisRow.length, i, separator); 30 | }); 31 | }); 32 | 33 | return csv; 34 | }; 35 | -------------------------------------------------------------------------------- /test/fixtures/options.js: -------------------------------------------------------------------------------- 1 | export const optionsHeaderSeparatorDefault = { 2 | header: ['Number', 'First', 'Last', 'Handle'], 3 | separator: ',', 4 | }; 5 | 6 | export const optionsHeaderWithSpacesSeparatorDefault = { 7 | header: ['number number', 'first', 'last', 'handle'], 8 | separator: ',', 9 | }; 10 | 11 | export const optionsHeaderSeparatorSemicolon = { 12 | header: ['Number', 'First', 'Last', 'Handle'], 13 | separator: ';', 14 | }; 15 | 16 | export const optionsHeaderDefaultSeparatorTab = { 17 | header: undefined, 18 | separator: '\t', 19 | }; 20 | 21 | export const optionsHeaderDefaultSeparatorPipe = { 22 | header: undefined, 23 | separator: '|', 24 | }; 25 | 26 | export const optionsHeaderDefaultSeparatorNewline = { 27 | header: undefined, 28 | separator: '\n', 29 | }; 30 | 31 | export const optionsDefault = { 32 | header: undefined, 33 | separator: ',', 34 | }; 35 | 36 | export const optionsHeaderBooleans = { 37 | header: ['description', 'value'], 38 | separator: ',', 39 | }; 40 | 41 | export const optionsHeaderZero = { 42 | header: [0, 'first', 'last', ''], 43 | separator: ',', 44 | }; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present Lukas Aichbauer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "convert-array-to-csv", 3 | "version": "2.0.0", 4 | "description": "Convert an array to a csv formatted string", 5 | "main": "./lib/index.js", 6 | "repository": "https://www.github.com/aichbauer/node-convert-array-to-csv", 7 | "author": "Aichbauer Lukas ", 8 | "license": "MIT", 9 | "private": false, 10 | "scripts": { 11 | "pretest": "npm run build && npm run lint", 12 | "lint": "eslint src", 13 | "test": "jest --coverage", 14 | "build": "babel src --out-dir lib", 15 | "prepush": "npm run test", 16 | "prepare": "npm run build" 17 | }, 18 | "keywords": [ 19 | "array", 20 | "csv", 21 | "list", 22 | "convert", 23 | "comma-separated", 24 | "values", 25 | "convert-array-to-csv" 26 | ], 27 | "jest": { 28 | "globals": { 29 | "__DEV__": true 30 | }, 31 | "testEnvironment": "node", 32 | "testPathIgnorePatterns": [ 33 | "/node_modules/" 34 | ] 35 | }, 36 | "devDependencies": { 37 | "@babel/cli": "^7.8.4", 38 | "@babel/core": "^7.9.0", 39 | "@babel/preset-env": "^7.9.5", 40 | "babel-core": "^7.0.0-0", 41 | "babel-jest": "^24.9.0", 42 | "eslint": "^5.5.0", 43 | "eslint-config-airbnb-base": "^13.1.0", 44 | "eslint-plugin-import": "^2.20.2", 45 | "husky": "^0.14.3", 46 | "jest": "^24.9.0", 47 | "regenerator-runtime": "^0.12.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0.0 - May, 17 2020 2 | 3 | * f5ebcde Fix: consider separator in checks and use double quotes correctly (closes #22) (Vano Devium) 4 | * fb85b50 CI: changed to 12x, 14x versions of Node.js for test pipeline (closes #31) (Vano Devium) 5 | 6 | 1.0.12 - May, 07 2020 7 | 8 | * edf27e8 Fix: incorrect check for false values (#30) (John Pariseau) 9 | 10 | 1.0.11 - April, 09 2020 11 | 12 | * 9dd684b Chore: update deps (#27) (Lukas Aichbauer) 13 | * 7ca8100 Refactor: Optimized helpers (#26) (Vano Devium) 14 | 15 | 1.0.10 - February, 14 2020 16 | 17 | * d95aa36 Chore: bump eslint-utils from 1.4.0 to 1.4.3 (#23) (dependabot[bot]) 18 | * 53bb4e9 Chore: bump handlebars from 4.1.2 to 4.5.3 (#24) (dependabot[bot]) 19 | 20 | 1.0.9 - August, 02 2019 21 | 22 | * 8ae2d19 Chore: switch yarn to npm (#21) (Lukas Aichbauer) 23 | 24 | 1.0.8 - May, 25 2019 25 | 26 | * bd592e5 Fix: zero (0) in header and body elements (#19) (Lukas Aichbauer) 27 | 28 | 1.0.7 - April, 25 2019 29 | 30 | * 17a7f6e Fix: handle numbers correct (#17) (closes #16) (Lukas Aichbauer) 31 | 32 | 1.0.6 - March, 29 2019 33 | 34 | * 6653259 Fix: replace every double quote (#15) (Lukas Aichbauer) 35 | 36 | 1.0.5 - February, 26 2019 37 | 38 | * 58ec063 Fix: double quotes inside elements (closes #9) (#12) (Lukas Aichbauer) 39 | * 2ea0633 Refactor: add append element and separator or line break functions (closes #8) (#11) (Lukas Aichbauer) 40 | * c237523 Fix: space as special char (closes #7) (#10) (Lukas Aichbauer) 41 | 42 | 1.0.4 - September, 07 2018 43 | 44 | * 2b92efa Chore: add prepush hook and changed prepublish to prepare (#6) (Lukas Aichbauer) 45 | 46 | 1.0.3 - September, 07 2018 47 | 48 | * 51a76a7 Fix: babel runtime (#5) (fixes #3) (Lukas Aichbauer) 49 | 50 | 1.0.2 - June, 22 2018 51 | 52 | * 4e024bd Fix : handle zero values correctly (array of arrays) (#4) (Kiwy404) 53 | 54 | 1.0.1 - May, 07 2018 55 | 56 | * 6e91d6d Fix: handle null and undefined values (#2) (Lukas Aichbauer) 57 | 58 | 1.0.0 - April, 05 2018 59 | 60 | * 48cd02c Fix: pass options from index to modules (#1) (Lukas Aichbauer) 61 | * ff5adcb Initial commit (aichbauer) 62 | 63 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import converter, { convertArrayToCSV } from '../src'; 2 | 3 | import { 4 | dataArrayWithoutHeader, 5 | dataObject, 6 | } from './fixtures/data'; 7 | 8 | import { 9 | expectedResultArrayNoHeaderNoOptions, 10 | expectedResultObjectNoOptions, 11 | expectedResultArrayHeaderWithSpaces, 12 | } from './fixtures/expected-results'; 13 | import { 14 | optionsHeaderWithSpacesSeparatorDefault, 15 | optionsHeaderDefaultSeparatorNewline, 16 | } from './fixtures/options'; 17 | 18 | test('convertArrayToCsv | array of arrays | with no header and no options', () => { 19 | const result = convertArrayToCSV(dataArrayWithoutHeader); 20 | 21 | expect(result).toBe(expectedResultArrayNoHeaderNoOptions); 22 | }); 23 | 24 | test('convertArrayToCsv | array of arrays | with header with spaces and no options', () => { 25 | const result = convertArrayToCSV(dataArrayWithoutHeader, optionsHeaderWithSpacesSeparatorDefault); 26 | 27 | expect(result).toBe(expectedResultArrayHeaderWithSpaces); 28 | }); 29 | 30 | test('convertArrayToCsv | array of objects | with no options', () => { 31 | const result = convertArrayToCSV(dataObject); 32 | 33 | expect(result).toBe(expectedResultObjectNoOptions); 34 | }); 35 | 36 | test('default export | array of arrays | with no header and no options', () => { 37 | const result = converter(dataArrayWithoutHeader); 38 | 39 | expect(result).toBe(expectedResultArrayNoHeaderNoOptions); 40 | }); 41 | 42 | test('default export | array of objects | with no options', () => { 43 | const result = converter(dataObject); 44 | 45 | expect(result).toBe(expectedResultObjectNoOptions); 46 | }); 47 | 48 | test('convertArrayToCsv | not an array | with no options', () => { 49 | const result = () => convertArrayToCSV({}); 50 | 51 | expect(result).toThrow('data has to be typeof: object and instanceof Array: true but got typeof: object and instanceof Array: false'); 52 | }); 53 | 54 | test('convertArrayToCsv | not an array | with no options', () => { 55 | const result = () => convertArrayToCSV(dataObject, optionsHeaderDefaultSeparatorNewline); 56 | 57 | expect(result).toThrow('The separator must be single-character and cannot be a newline or double quotes'); 58 | }); 59 | -------------------------------------------------------------------------------- /test/fixtures/expected-results.js: -------------------------------------------------------------------------------- 1 | export const expectedResultArrayNoHeaderNoOptions = '1,Mark,Otto,@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 2 | 3 | export const expectedResultObjectNoOptions = 'number,first,last,handle\n1,Mark,Otto,@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 4 | 5 | export const expectedResultObjectOnlyHeader = 'Number,First,Last,Handle\n1,Mark,Otto,@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 6 | 7 | export const expectedResultObjectNullAndUndefined = 'Number,First,Last,Handle\n1,Mark,"",@mdo\n2,Jacob,Thornton,""\n3,Larry,the Bird,@twitter\n'; 8 | 9 | export const expectedResultArrayNullAndUndefined = 'number,first,last,handle\n1,Mark,"",@mdo\n2,Jacob,Thornton,""\n3,Larry,the Bird,@twitter\n'; 10 | 11 | export const expectedResultArrayWithFloats = 'number,first,last,handle\n1.001,Mark,Otto,@mdo\n2.002,Jacob,Thornton,@fat\n3.33,Larry,the Bird,@twitter\n'; 12 | 13 | export const expectedResultObjectWithFloats = 'number,first,last,handle\n1.001,Mark,Otto,@mdo\n2.002,Jacob,Thornton,@fat\n3.33,Larry,the Bird,@twitter\n'; 14 | 15 | export const expectedResultArrayBooleans = 'description,value\ntrue as boolean,true\nfalse as boolean,false\ntrue as string,true\nfalse as string,false\n1 as number,1\n0 as number,0\n1 as string,1\n0 as string,0\nNaN,""\nempty string,""\n'; 16 | 17 | export const expectedResultArrayZero = '0,first,last,""\n0,Mark,Otto,@mdo\n1,Jacob,Thornton,@fat\n2,Larry,the Bird,@twitter\n'; 18 | 19 | export const expectedResultObjectZero = '0,first,last,""\n0,Mark,Otto,@mdo\n1,Jacob,Thornton,@fat\n2,Larry,the Bird,@twitter\n'; 20 | 21 | export const expectedResultObjectHeaderSeparatorSemicolon = 'Number;First;Last;Handle\n1;Mark;Otto;@mdo\n2;Jacob;Thornton;@fat\n3;Larry;the Bird;@twitter\n'; 22 | 23 | export const expectedResultObjectOnlySeparatorTab = 'number\tfirst\tlast\thandle\n1\tMark\tOtto\t@mdo\n2\tJacob\tThornton\t@fat\n3\tLarry\tthe Bird\t@twitter\n'; 24 | 25 | export const expectedResultObjectOnlySeparatorPipe = 'number|first|last|handle\n1|"Mark | 1"|Otto|@mdo\n2|"Jacob | 2"|Thornton|@fat\n3|"Larry | 3"|the Bird|@twitter\n'; 26 | 27 | export const expectedResultArrayHeaderWithSpaces = 'number number,first,last,handle\n1,Mark,Otto,@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 28 | 29 | export const expectedResultArrayWithDoubleQuotesInsideElement = '1,Mark,"Ot""t""o",@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 30 | 31 | export const expectedResultArrayWithHeaderNoOptions = expectedResultObjectNoOptions; 32 | 33 | export const expectedResultArrayOnlyHeader = expectedResultObjectOnlyHeader; 34 | 35 | export const expectedResultArrayHeaderSeparatorSemicolon = expectedResultObjectHeaderSeparatorSemicolon; // eslint-disable-line 36 | 37 | export const expectedResultArrayOnlySeparatorTab = expectedResultObjectOnlySeparatorTab; 38 | 39 | export const expectedResultArrayOnlySeparatorPipe = expectedResultObjectOnlySeparatorPipe; 40 | 41 | export const expectedResultObjectWithDoubleQuotesInsideElement = `number,first,last,handle\n${expectedResultArrayWithDoubleQuotesInsideElement}`; // eslint-disable-line 42 | -------------------------------------------------------------------------------- /test/helpers/check-special-chars-and-empty.spec.js: -------------------------------------------------------------------------------- 1 | import { checkSpecialCharsAndEmpty } from '../../src/helpers/check-special-chars-and-empty'; 2 | 3 | test('checkIfValid | newline', () => { 4 | const result = checkSpecialCharsAndEmpty('\n'); 5 | 6 | expect(result).toBeTruthy(); 7 | }); 8 | 9 | test('checkIfValid | tab, tab separator', () => { 10 | const result = checkSpecialCharsAndEmpty('\t', '\t'); 11 | 12 | expect(result).toBeTruthy(); 13 | }); 14 | 15 | test('checkIfValid | tab, default separator', () => { 16 | const result = checkSpecialCharsAndEmpty('\t'); 17 | 18 | expect(result).toBeFalsy(); 19 | }); 20 | 21 | test('checkIfValid | comma, comma separator', () => { 22 | const result = checkSpecialCharsAndEmpty(',', ','); 23 | 24 | expect(result).toBeTruthy(); 25 | }); 26 | 27 | test('checkIfValid | comma, default separator', () => { 28 | const result = checkSpecialCharsAndEmpty(','); 29 | 30 | expect(result).toBeFalsy(); 31 | }); 32 | 33 | test('checkIfValid | semicolon, semicolon separator', () => { 34 | const result = checkSpecialCharsAndEmpty(';', ';'); 35 | 36 | expect(result).toBeTruthy(); 37 | }); 38 | 39 | test('checkIfValid | semicolon, default separator', () => { 40 | const result = checkSpecialCharsAndEmpty(';'); 41 | 42 | expect(result).toBeFalsy(); 43 | }); 44 | 45 | test('checkIfValid | dot, dot separator', () => { 46 | const result = checkSpecialCharsAndEmpty('.', '.'); 47 | 48 | expect(result).toBeTruthy(); 49 | }); 50 | 51 | test('checkIfValid | dot, default separator', () => { 52 | const result = checkSpecialCharsAndEmpty('.'); 53 | 54 | expect(result).toBeFalsy(); 55 | }); 56 | 57 | test('checkIfValid | single quote, single quote separator', () => { 58 | const result = checkSpecialCharsAndEmpty('\'', '\''); 59 | 60 | expect(result).toBeTruthy(); 61 | }); 62 | 63 | test('checkIfValid | single quote, default separator', () => { 64 | const result = checkSpecialCharsAndEmpty('\''); 65 | 66 | expect(result).toBeFalsy(); 67 | }); 68 | 69 | test('checkIfValid | no value', () => { 70 | const result = checkSpecialCharsAndEmpty(''); 71 | 72 | expect(result).toBeTruthy(); 73 | }); 74 | 75 | test('checkIfValid | pipe, pipe separator', () => { 76 | const result = checkSpecialCharsAndEmpty('|', '|'); 77 | 78 | expect(result).toBeTruthy(); 79 | }); 80 | 81 | test('checkIfValid | pipe, default separator', () => { 82 | const result = checkSpecialCharsAndEmpty('|'); 83 | 84 | expect(result).toBeFalsy(); 85 | }); 86 | 87 | test('checkIfValid | normal value', () => { 88 | const result = checkSpecialCharsAndEmpty('hello'); 89 | 90 | expect(result).toBeFalsy(); 91 | }); 92 | 93 | test('checkIfValid | double quote, double quote separator', () => { 94 | const result = checkSpecialCharsAndEmpty('"', '"'); 95 | 96 | expect(result).toBeTruthy(); 97 | }); 98 | 99 | test('checkIfValid | double quote, default separator', () => { 100 | const result = checkSpecialCharsAndEmpty('"'); 101 | 102 | expect(result).toBeTruthy(); 103 | }); 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # convert-array-to-csv 2 | 3 | [![npm](https://img.shields.io/npm/v/convert-array-to-csv.svg?style=flat-square)](https://www.npmjs.com/package/convert-array-to-csv) 4 | [![Travis branch](https://img.shields.io/travis/aichbauer/node-convert-array-to-csv/master.svg?style=flat-square)](https://travis-ci.org/aichbauer/node-convert-array-to-csv) 5 | [![Codecov branch](https://img.shields.io/codecov/c/github/aichbauer/node-convert-array-to-csv/master.svg?style=flat-square)](https://codecov.io/gh/aichbauer/node-convert-array-to-csv) 6 | 7 | > Convert an array to a csv formatted string 8 | 9 | ## Table of Contents 10 | 11 | * [Why?](#why) 12 | * [Installation](#installation) 13 | * [Functions](#functions) 14 | * [Usage](#usage) 15 | * [License](#license) 16 | 17 | ## Why? 18 | 19 | I needed a simple way to download the data from a table component in a csv format. 20 | 21 | ## Installation 22 | 23 | ```sh 24 | $ npm i convert-array-to-csv -S 25 | ``` 26 | 27 | or 28 | 29 | ```sh 30 | $ yarn add convert-array-to-csv 31 | ``` 32 | 33 | ## Functions 34 | 35 | Take a look into the [usage section](#usage) for a detailed example. 36 | 37 | ### convertArrayToCSV 38 | 39 | > Note: you can also use the default export. 40 | 41 | This function converts an array of objects, or an array of arrays into an csv formatted string. 42 | 43 | #### Syntax 44 | 45 | Returns a new string. 46 | 47 | ```js 48 | const csv = convertArrayToCSV(data, options); 49 | ``` 50 | 51 | ##### Parameters 52 | 53 | * data: an array of arrays or an array of objects 54 | * options: a object 55 | * holds two keys: header and separator 56 | * **header**: and array with the name of the columns, default: `undefined` 57 | * **separator**: the character which is the separator in your csv formatted string, default: `','` 58 | 59 | ## Usage 60 | 61 | An example how to use it. 62 | 63 | ```js 64 | const { convertArrayToCSV } = require('convert-array-to-csv'); 65 | const converter = require('convert-array-to-csv'); 66 | 67 | const header = ['number', 'first', 'last', 'handle']; 68 | const dataArrays = [ 69 | [1, 'Mark', 'Otto', '@mdo'], 70 | [2, 'Jacob', 'Thornton', '@fat'], 71 | [3, 'Larry', 'the Bird', '@twitter'], 72 | ]; 73 | const dataObjects = [ 74 | { 75 | number: 1, 76 | first: 'Mark', 77 | last: 'Otto', 78 | handle: '@mdo', 79 | }, 80 | { 81 | number: 2, 82 | first: 'Jacob', 83 | last: 'Thornton', 84 | handle: '@fat', 85 | }, 86 | { 87 | number: 3, 88 | first: 'Larry', 89 | last: 'the Bird', 90 | handle: '@twitter', 91 | }, 92 | ]; 93 | 94 | /* 95 | const csvFromArrayOfObjects = 'number,first,last,handle\n1,Mark,Otto,@mdo\n2,Jacob,Thornton,@fat\n3,Larry,the Bird,@twitter\n'; 96 | */ 97 | const csvFromArrayOfObjects = convertArrayToCSV(dataObjects); 98 | 99 | /* 100 | const csvFromArrayOfArrays = 'number;first;last;handle\n1;Mark;Otto;@mdo\n2;Jacob;Thornton;@fat\n3;Larry;the Bird;@twitter\n'; 101 | */ 102 | const csvFromArrayOfArrays = convertArrayToCSV(dataArrays, { 103 | header, 104 | separator: ';' 105 | }); 106 | ``` 107 | 108 | ## License 109 | 110 | MIT © Lukas Aichbauer 111 | -------------------------------------------------------------------------------- /test/modules/convert-array-of-objects-to-csv.spec.js: -------------------------------------------------------------------------------- 1 | import { convertArrayOfObjectsToCSV } from '../../src/modules/convert-array-of-objects-to-csv'; 2 | 3 | import { 4 | dataObject, 5 | dataObjectWithNullAndUndefined, 6 | dataObjectWithDoubleQuotesInsideElement, 7 | dataObjectWithFloat, 8 | dataObjectWith0, 9 | } from '../fixtures/data'; 10 | import { 11 | optionsHeaderSeparatorSemicolon, 12 | optionsHeaderSeparatorDefault, 13 | optionsHeaderDefaultSeparatorTab, 14 | optionsDefault, 15 | optionsHeaderZero, 16 | } from '../fixtures/options'; 17 | 18 | import { 19 | expectedResultObjectNoOptions, 20 | expectedResultObjectHeaderSeparatorSemicolon, 21 | expectedResultObjectOnlyHeader, 22 | expectedResultObjectOnlySeparatorTab, 23 | expectedResultObjectNullAndUndefined, 24 | expectedResultObjectWithDoubleQuotesInsideElement, 25 | expectedResultObjectWithFloats, 26 | expectedResultObjectZero, 27 | } from '../fixtures/expected-results'; 28 | 29 | test('convertArrayOfObjectsToCSV | array of objects | with default options', () => { 30 | const result = convertArrayOfObjectsToCSV(dataObjectWithDoubleQuotesInsideElement, optionsDefault); // eslint-disable-line 31 | 32 | expect(result).toBe(expectedResultObjectWithDoubleQuotesInsideElement); 33 | }); 34 | 35 | test('convertArrayOfObjectsToCSV | array of objects | with default options and double quotes in element', () => { 36 | const result = convertArrayOfObjectsToCSV(dataObject, optionsDefault); 37 | 38 | expect(result).toBe(expectedResultObjectNoOptions); 39 | }); 40 | 41 | test('convertArrayOfObjectsToCSV | array of objects | options: header + separator semicolon', () => { 42 | const result = convertArrayOfObjectsToCSV(dataObject, optionsHeaderSeparatorSemicolon); 43 | 44 | expect(result).toBe(expectedResultObjectHeaderSeparatorSemicolon); 45 | }); 46 | 47 | test('convertArrayOfObjectsToCSV | array of objects | options: header + default separator', () => { 48 | const result = convertArrayOfObjectsToCSV(dataObject, optionsHeaderSeparatorDefault); 49 | 50 | expect(result).toBe(expectedResultObjectOnlyHeader); 51 | }); 52 | 53 | test('convertArrayOfObjectsToCSV | array of objects | options: default header + separator tab', () => { 54 | const result = convertArrayOfObjectsToCSV(dataObject, optionsHeaderDefaultSeparatorTab); 55 | 56 | expect(result).toBe(expectedResultObjectOnlySeparatorTab); 57 | }); 58 | 59 | test('convertArrayOfObjectsToCSV | array of objects with values of null and undefined | options: header + default separator', () => { 60 | const result = convertArrayOfObjectsToCSV( 61 | dataObjectWithNullAndUndefined, 62 | optionsHeaderSeparatorDefault, 63 | ); 64 | 65 | expect(result).toBe(expectedResultObjectNullAndUndefined); 66 | }); 67 | 68 | test('convertArrayOfObjectsToCSV | array of objects with float | options: default header', () => { 69 | const result = convertArrayOfObjectsToCSV(dataObjectWithFloat, optionsDefault); 70 | 71 | expect(result).toBe(expectedResultObjectWithFloats); 72 | }); 73 | 74 | test('convertArrayOfObjectsToCSV | array of objects with value of zero | options: header with zero and "" + default separator', () => { 75 | const result = convertArrayOfObjectsToCSV(dataObjectWith0, optionsHeaderZero); 76 | 77 | expect(result).toBe(expectedResultObjectZero); 78 | }); 79 | 80 | test('convertArrayOfObjectsToCSV | array of objects with value of zero | options: default header + default separator', () => { 81 | const result = convertArrayOfObjectsToCSV(dataObjectWith0, optionsDefault); 82 | 83 | expect(result).toBe(expectedResultObjectZero); 84 | }); 85 | -------------------------------------------------------------------------------- /test/fixtures/data.js: -------------------------------------------------------------------------------- 1 | const headerArray = [ 2 | ['number', 'first', 'last', 'handle'], 3 | ]; 4 | 5 | const dataArray = [ 6 | [1, 'Mark', 'Otto', '@mdo'], 7 | [2, 'Jacob', 'Thornton', '@fat'], 8 | [3, 'Larry', 'the Bird', '@twitter'], 9 | ]; 10 | 11 | const dataArrayWithDoubleQuotes = [ 12 | [1, 'Mark', 'Ot"t"o', '@mdo'], 13 | [2, 'Jacob', 'Thornton', '@fat'], 14 | [3, 'Larry', 'the Bird', '@twitter'], 15 | ]; 16 | 17 | const dataArrayWithNullAndUndefined = [ 18 | [1, 'Mark', null, '@mdo'], 19 | [2, 'Jacob', 'Thornton', undefined], 20 | [3, 'Larry', 'the Bird', '@twitter'], 21 | ]; 22 | 23 | const dataArrayWithZero = [ 24 | [0, 'Mark', 'Otto', '@mdo'], 25 | [1, 'Jacob', 'Thornton', '@fat'], 26 | [2, 'Larry', 'the Bird', '@twitter'], 27 | ]; 28 | 29 | const dataArrayWithFloat = [ 30 | [1.001, 'Mark', 'Otto', '@mdo'], 31 | [2.002, 'Jacob', 'Thornton', '@fat'], 32 | [3.33, 'Larry', 'the Bird', '@twitter'], 33 | ]; 34 | 35 | const dataArrayWithBooleans = [ 36 | ['true as boolean', true], 37 | ['false as boolean', false], 38 | ['true as string', 'true'], 39 | ['false as string', 'false'], 40 | ['1 as number', 1], 41 | ['0 as number', 0], 42 | ['1 as string', '1'], 43 | ['0 as string', '0'], 44 | ['NaN', NaN], 45 | ['empty string', ''], 46 | ]; 47 | 48 | const dataArrayWithPipe = [ 49 | [1, 'Mark | 1', 'Otto', '@mdo'], 50 | [2, 'Jacob | 2', 'Thornton', '@fat'], 51 | [3, 'Larry | 3', 'the Bird', '@twitter'], 52 | ]; 53 | 54 | const data = [ 55 | { 56 | number: 1, 57 | first: 'Mark', 58 | last: 'Otto', 59 | handle: '@mdo', 60 | }, 61 | { 62 | number: 2, 63 | first: 'Jacob', 64 | last: 'Thornton', 65 | handle: '@fat', 66 | }, 67 | { 68 | number: 3, 69 | first: 'Larry', 70 | last: 'the Bird', 71 | handle: '@twitter', 72 | }, 73 | ]; 74 | 75 | const dataObjectWithDoubleQuotes = [ 76 | { 77 | number: 1, 78 | first: 'Mark', 79 | last: 'Ot"t"o', 80 | handle: '@mdo', 81 | }, 82 | { 83 | number: 2, 84 | first: 'Jacob', 85 | last: 'Thornton', 86 | handle: '@fat', 87 | }, 88 | { 89 | number: 3, 90 | first: 'Larry', 91 | last: 'the Bird', 92 | handle: '@twitter', 93 | }, 94 | ]; 95 | 96 | const dataWithNullAndUndefined = [ 97 | { 98 | number: 1, 99 | first: 'Mark', 100 | last: null, 101 | handle: '@mdo', 102 | }, 103 | { 104 | number: 2, 105 | first: 'Jacob', 106 | last: 'Thornton', 107 | handle: undefined, 108 | }, 109 | { 110 | number: 3, 111 | first: 'Larry', 112 | last: 'the Bird', 113 | handle: '@twitter', 114 | }, 115 | ]; 116 | 117 | const dataWithFloat = [ 118 | { 119 | number: 1.001, 120 | first: 'Mark', 121 | last: 'Otto', 122 | handle: '@mdo', 123 | }, 124 | { 125 | number: 2.002, 126 | first: 'Jacob', 127 | last: 'Thornton', 128 | handle: '@fat', 129 | }, 130 | { 131 | number: 3.33, 132 | first: 'Larry', 133 | last: 'the Bird', 134 | handle: '@twitter', 135 | }, 136 | ]; 137 | 138 | const dataWith0 = [ 139 | { 140 | 0: 0, 141 | first: 'Mark', 142 | last: 'Otto', 143 | '': '@mdo', 144 | }, 145 | { 146 | 0: 1, 147 | first: 'Jacob', 148 | last: 'Thornton', 149 | '': '@fat', 150 | }, 151 | { 152 | 0: 2, 153 | first: 'Larry', 154 | last: 'the Bird', 155 | '': '@twitter', 156 | }, 157 | ]; 158 | 159 | export const dataArrayWithHeader = [ 160 | ...headerArray, 161 | ...dataArray, 162 | ]; 163 | 164 | export const dataArrayWithDoubleQuotesInsideElement = [ 165 | ...dataArrayWithDoubleQuotes, 166 | ]; 167 | 168 | export const dataArrayWithoutHeader = [ 169 | ...dataArray, 170 | ]; 171 | 172 | export const dataObject = [ 173 | ...data, 174 | ]; 175 | 176 | export const dataObjectWithDoubleQuotesInsideElement = [ 177 | ...dataObjectWithDoubleQuotes, 178 | ]; 179 | 180 | export const dataObjectWithNullAndUndefined = [ 181 | ...dataWithNullAndUndefined, 182 | ]; 183 | 184 | export const dataObjectWithFloat = [ 185 | ...dataWithFloat, 186 | ]; 187 | 188 | export const dataObjectWith0 = [ 189 | ...dataWith0, 190 | ]; 191 | 192 | export const dataArrayWithHeaderAndNullAndUndefined = [ 193 | ...headerArray, 194 | ...dataArrayWithNullAndUndefined, 195 | ]; 196 | 197 | export const dataArrayWithHeaderAndFloats = [ 198 | ...headerArray, 199 | ...dataArrayWithFloat, 200 | ]; 201 | 202 | export const dataArrayWithHeaderWithBooleans = [ 203 | ...dataArrayWithBooleans, 204 | ]; 205 | 206 | export const dataArrayWithHeaderAndZero = [ 207 | ...dataArrayWithZero, 208 | ]; 209 | 210 | export const dataArrayWithHeaderAndPipe = [ 211 | ...headerArray, 212 | ...dataArrayWithPipe, 213 | ]; 214 | -------------------------------------------------------------------------------- /test/modules/convert-array-of-arrays-to-csv.spec.js: -------------------------------------------------------------------------------- 1 | import { convertArrayOfArraysToCSV } from '../../src/modules/convert-array-of-arrays-to-csv'; 2 | 3 | import { 4 | dataArrayWithHeader, 5 | dataArrayWithoutHeader, 6 | dataArrayWithHeaderAndNullAndUndefined, 7 | dataArrayWithHeaderWithBooleans, 8 | dataArrayWithHeaderAndZero, 9 | dataArrayWithDoubleQuotesInsideElement, 10 | dataArrayWithHeaderAndFloats, 11 | dataArrayWithHeaderAndPipe, 12 | } from '../fixtures/data'; 13 | import { 14 | optionsHeaderSeparatorSemicolon, 15 | optionsHeaderSeparatorDefault, 16 | optionsHeaderDefaultSeparatorTab, 17 | optionsHeaderDefaultSeparatorPipe, 18 | optionsDefault, 19 | optionsHeaderBooleans, 20 | optionsHeaderZero, 21 | } from '../fixtures/options'; 22 | 23 | import { 24 | expectedResultArrayWithHeaderNoOptions, 25 | expectedResultArrayNoHeaderNoOptions, 26 | expectedResultArrayOnlyHeader, 27 | expectedResultArrayHeaderSeparatorSemicolon, 28 | expectedResultArrayOnlySeparatorTab, 29 | expectedResultArrayNullAndUndefined, 30 | expectedResultArrayBooleans, 31 | expectedResultArrayZero, 32 | expectedResultArrayWithDoubleQuotesInsideElement, 33 | expectedResultArrayWithFloats, 34 | expectedResultArrayOnlySeparatorPipe, 35 | } from '../fixtures/expected-results'; 36 | 37 | test('convertArrayOfArraysToCSV | array of arrays | with default options', () => { 38 | const result = convertArrayOfArraysToCSV(dataArrayWithHeader, optionsDefault); 39 | 40 | expect(result).toBe(expectedResultArrayWithHeaderNoOptions); 41 | }); 42 | 43 | test('convertArrayOfArraysToCSV | array of arrays | with default options and double quotes in element', () => { 44 | const result = convertArrayOfArraysToCSV(dataArrayWithDoubleQuotesInsideElement, optionsDefault); 45 | 46 | expect(result).toBe(expectedResultArrayWithDoubleQuotesInsideElement); 47 | }); 48 | 49 | test('convertArrayOfArraysToCSV | array of arrays | with default options and no header', () => { 50 | const result = convertArrayOfArraysToCSV(dataArrayWithoutHeader, optionsDefault); 51 | 52 | expect(result).toBe(expectedResultArrayNoHeaderNoOptions); 53 | }); 54 | 55 | test('convertArrayOfArraysToCSV | array of arrays | with default options and no header', () => { 56 | const result = convertArrayOfArraysToCSV(dataArrayWithoutHeader, optionsDefault); 57 | 58 | expect(result).toBe(expectedResultArrayNoHeaderNoOptions); 59 | }); 60 | 61 | test('convertArrayOfArraysToCSV | array of arrays | options: header + separator semicolon', () => { 62 | const result = convertArrayOfArraysToCSV(dataArrayWithoutHeader, optionsHeaderSeparatorSemicolon); 63 | 64 | expect(result).toBe(expectedResultArrayHeaderSeparatorSemicolon); 65 | }); 66 | 67 | test('convertArrayOfArraysToCSV | array of arrays | options: header + default separator', () => { 68 | const result = convertArrayOfArraysToCSV(dataArrayWithoutHeader, optionsHeaderSeparatorDefault); 69 | 70 | expect(result).toBe(expectedResultArrayOnlyHeader); 71 | }); 72 | 73 | test('convertArrayOfArraysToCSV | array of arrays | options: default header + separator tab', () => { 74 | const result = convertArrayOfArraysToCSV(dataArrayWithHeader, optionsHeaderDefaultSeparatorTab); 75 | 76 | expect(result).toBe(expectedResultArrayOnlySeparatorTab); 77 | }); 78 | 79 | test('convertArrayOfArraysToCSV | array of arrays | options: default header + separator pipe', () => { 80 | const result = convertArrayOfArraysToCSV( 81 | dataArrayWithHeaderAndPipe, 82 | optionsHeaderDefaultSeparatorPipe, 83 | ); 84 | 85 | expect(result).toBe(expectedResultArrayOnlySeparatorPipe); 86 | }); 87 | 88 | test('convertArrayOfArraysToCSV | array of arrays with values of null and undefined | options: header + default separator', () => { 89 | const result = convertArrayOfArraysToCSV(dataArrayWithHeaderAndNullAndUndefined, optionsDefault); 90 | 91 | expect(result).toBe(expectedResultArrayNullAndUndefined); 92 | }); 93 | 94 | test('convertArrayOfArraysToCSV | array of arrays with boolean values | with default options and header', () => { 95 | const result = convertArrayOfArraysToCSV(dataArrayWithHeaderWithBooleans, optionsHeaderBooleans); 96 | 97 | expect(result).toBe(expectedResultArrayBooleans); 98 | }); 99 | 100 | test('convertArrayOfArraysToCSV | array of arrays with value of zero | with default options and header', () => { 101 | const result = convertArrayOfArraysToCSV(dataArrayWithHeaderAndZero, optionsHeaderZero); 102 | 103 | expect(result).toBe(expectedResultArrayZero); 104 | }); 105 | 106 | test('convertArrayOfArraysToCSV | array of arrays with floats | with default options and header', () => { 107 | const result = convertArrayOfArraysToCSV(dataArrayWithHeaderAndFloats, optionsDefault); 108 | 109 | expect(result).toBe(expectedResultArrayWithFloats); 110 | }); 111 | --------------------------------------------------------------------------------