jsonexport
Advanced tools
Comparing version 1.4.2 to 1.5.0
297
lib/index.js
@@ -0,1 +1,2 @@ | ||
/* jshint node:true */ | ||
'use strict'; | ||
@@ -8,2 +9,6 @@ /** | ||
var joinRows = require('./join-rows'); | ||
var generateHeaders; | ||
var escapeDelimiters; | ||
var options; | ||
@@ -28,2 +33,11 @@ | ||
options = parseOptions(userOptions); | ||
escapeDelimiters = require('./escape-delimiters')( | ||
options.textDelimiter, | ||
options.rowDelimiter, | ||
options.endOfLine | ||
); | ||
generateHeaders = require('./generate-headers')(escapeDelimiters); | ||
generateCsv(json, callback); | ||
@@ -46,3 +60,3 @@ }; | ||
undefinedString: '', // String | ||
endOfLine: null, // String | ||
endOfLine: os.EOL || '\n', // String | ||
mainPathItem: null, // String | ||
@@ -55,6 +69,6 @@ booleanTrueString: null, // String | ||
//Handlers | ||
handleString: null, // Function | ||
handleNumber: null, // Function | ||
handleBoolean: null, // Function | ||
handleDate: null, // Function | ||
handleString: handleString, // Function | ||
handleNumber: handleNumber, // Function | ||
handleBoolean: handleBoolean, // Function | ||
handleDate: handleDate, // Function | ||
//handleArray: null, // Function | ||
@@ -75,135 +89,108 @@ //handleObject: null // Function | ||
function generateCsv(json, callback) { | ||
//Check if the passed json is an Array | ||
if(_.isArray(json)){ | ||
var parseResult = []; | ||
json.forEach(function (item) { | ||
//Call checkType to list all items inside this object | ||
//Items are returned as a object {item: 'Prop Value, Item Name', | ||
// value: 'Prop Data Value'} | ||
var itemResult = checkType(item, options.mainPathItem); | ||
parseResult.push(itemResult); | ||
}); | ||
return callback(null, generateFromArray(json)); | ||
} | ||
else if(_.isObject(json)){ | ||
return callback(null, generateFromObject(json)); | ||
} | ||
//Generate the csv headers based on prop usage over the elements | ||
var headers = generateHeaders(parseResult); | ||
//Generate the csv output | ||
var fileRows = []; | ||
if(options.includeHeaders){ | ||
//Add the headers to the first line | ||
fileRows.push(headers.join(options.rowDelimiter)); | ||
} | ||
return callback(new Error('Unable to parse the JSON object, its not an Array or Object.')); | ||
} | ||
parseResult.forEach(function (result) { | ||
//Initialize the array with empty strings to handle 'unpopular' headers | ||
var resultRows = [Array(headers.length).join(".").split(".")]; | ||
function generateFromArray(json) { | ||
var fileRows = []; | ||
var parseResult = []; | ||
var outputFile; | ||
var fillRows; | ||
json.forEach(function (item) { | ||
//Call checkType to list all items inside this object | ||
//Items are returned as a object {item: 'Prop Value, Item Name', | ||
// value: 'Prop Data Value'} | ||
var itemResult = checkType(item, options.mainPathItem); | ||
parseResult.push(itemResult); | ||
}); | ||
result.forEach(function(element){ | ||
var placed = false; | ||
resultRows.forEach(function (row) { | ||
if(!placed && row[headers.indexOf(element.item)] == ''){ | ||
row[headers.indexOf(element.item)] = element.value; | ||
placed = true; | ||
} | ||
}); | ||
//Generate the csv headers based on prop usage over the elements | ||
var headers = generateHeaders(parseResult, options.orderHeaders); | ||
//Generate the csv output | ||
if(!placed){ | ||
var newRow = Array(headers.length).join(".").split("."); | ||
newRow[headers.indexOf(element.item)] = element.value; | ||
resultRows.push(newRow); | ||
} | ||
}); | ||
resultRows.forEach(function (row) { | ||
fileRows.push(row.join(options.rowDelimiter)); | ||
}); | ||
}); | ||
//Merge all rows in a single output with the correct End of Line string | ||
var outputFile = fileRows.join(options.endOfLine || os.EOL || '\n'); | ||
return callback(null, outputFile); | ||
} | ||
//Check if the passed json is an Object | ||
else if(_.isObject(json)){ | ||
var fileRows = []; | ||
var horizontalRows = [[],[]]; | ||
var textDelimiterRegex = new RegExp("\\" + options.textDelimiter, 'g'); | ||
var endOfLine = options.endOfLine || os.EOL || '\n'; | ||
for(var prop in json){ | ||
var prefix = ""; | ||
if(options.mainPathItem) | ||
prefix = options.mainPathItem + options.headerPathString; | ||
var parseResult = checkType(json[prop], prefix + prop); | ||
parseResult.forEach(function (result) { | ||
if(options.includeHeaders){ | ||
//Add the headers to the first line | ||
fileRows.push(headers.join(options.rowDelimiter)); | ||
} | ||
var value = result.value ? result.value.toString() : options.undefinedString; | ||
// Escape the textDelimiters contained in the field | ||
/*(https://tools.ietf.org/html/rfc4180) | ||
7. If double-quotes are used to enclose fields, then a double-quote | ||
appearing inside a field must be escaped by preceding it with | ||
another double quote. | ||
For example: "aaa","b""bb","ccc" | ||
*/ | ||
value = value.replace(textDelimiterRegex, options.textDelimiter+options.textDelimiter); | ||
// Escape the whole field if it contains a rowDelimiter or a linebreak | ||
if (value.indexOf(options.rowDelimiter) >= 0 || value.indexOf(endOfLine) >= 0) { | ||
value = options.textDelimiter + value + options.textDelimiter; | ||
} | ||
//Type header;value | ||
if(options.verticalOutput){ | ||
var row = [result.item, value]; | ||
fileRows.push(row.join(options.rowDelimiter)); | ||
}else{ | ||
horizontalRows[0].push(result.item); | ||
horizontalRows[1].push(value); | ||
} | ||
}); | ||
} | ||
if(!options.verticalOutput){ | ||
fileRows.push(horizontalRows[0].join(options.rowDelimiter)); | ||
fileRows.push(horizontalRows[1].join(options.rowDelimiter)); | ||
} | ||
//Merge all rows in a single output with the correct End of Line string | ||
var outputFile = fileRows.join(options.endOfLine || os.EOL || '\n'); | ||
return callback(null, outputFile); | ||
} | ||
fillRows = function (result) { | ||
//Initialize the array with empty strings to handle 'unpopular' headers | ||
var resultRows = [Array(headers.length).join(".").split(".")]; | ||
return callback(new Error('Unable to parse the JSON object, its not an Array or Object.')); | ||
result.forEach(function(element){ | ||
var placed = false; | ||
resultRows.forEach(function (row) { | ||
if(!placed && row[headers.indexOf(element.item)] === ''){ | ||
row[headers.indexOf(element.item)] = escapeDelimiters(element.value); | ||
placed = true; | ||
} | ||
}); | ||
if(!placed){ | ||
var newRow = Array(headers.length).join(".").split("."); | ||
newRow[headers.indexOf(element.item)] = escapeDelimiters(element.value); | ||
resultRows.push(newRow); | ||
} | ||
}); | ||
resultRows.forEach(function (row) { | ||
fileRows.push(row.join(options.rowDelimiter)); | ||
}); | ||
}; | ||
parseResult.forEach(fillRows); | ||
return joinRows(fileRows, options.endOfLine); | ||
} | ||
/** | ||
* Generate the file headers with optional sorting by header usage | ||
* | ||
* @param {Array} parseResult | ||
* @returns {Array} headers | ||
*/ | ||
function generateHeaders(parseResult){ | ||
var headers = []; | ||
var headerCount = []; | ||
//Check all the parsed json results | ||
parseResult.forEach(function (result) { | ||
result.forEach(function (element) { | ||
if(headerCount[element.item]) | ||
headerCount[element.item] += 1; | ||
else | ||
headerCount[element.item] = 1; | ||
}) | ||
}); | ||
var headerSort = []; | ||
//Create a sortable collection of headers | ||
for(var header in headerCount){ | ||
if(headerCount.hasOwnProperty(header)){ | ||
headerSort.push({ | ||
name: header, | ||
count: headerCount[header] | ||
}); | ||
} | ||
} | ||
if(options.orderHeaders){ | ||
//Sort the headers based on the count. | ||
headerSort = _.sortBy(headerSort,'count').reverse(); | ||
} | ||
//Create the headers list | ||
headerSort.forEach(function (header) { | ||
headers.push(header.name); | ||
}); | ||
return headers; | ||
function generateFromObject(json) { | ||
var fileRows = []; | ||
var parseResult = []; | ||
var outputFile; | ||
var fillRows; | ||
var horizontalRows = [[],[]]; | ||
fillRows = function (result) { | ||
var value = result.value ? result.value.toString() : options.undefinedString; | ||
value = escapeDelimiters(value); | ||
//Type header;value | ||
if(options.verticalOutput){ | ||
var row = [result.item, value]; | ||
fileRows.push(row.join(options.rowDelimiter)); | ||
}else{ | ||
horizontalRows[0].push(result.item); | ||
horizontalRows[1].push(value); | ||
} | ||
}; | ||
for(var prop in json){ | ||
var prefix = ""; | ||
if(options.mainPathItem) | ||
prefix = options.mainPathItem + options.headerPathString; | ||
parseResult = checkType(json[prop], prefix + prop); | ||
parseResult.forEach(fillRows); | ||
} | ||
if(!options.verticalOutput){ | ||
fileRows.push(horizontalRows[0].join(options.rowDelimiter)); | ||
fileRows.push(horizontalRows[1].join(options.rowDelimiter)); | ||
} | ||
return joinRows(fileRows, options.endOfLine); | ||
} | ||
function headers(result, item) { | ||
if (!item) return result; | ||
return result.map(function (element) { | ||
element.item = element.item ? item + options.headerPathString + element.item : item; | ||
return element; | ||
}); | ||
} | ||
/** | ||
@@ -216,34 +203,30 @@ * Check the element type of the element call the correct handle function | ||
function checkType(element, item){ | ||
var result = []; | ||
var result; | ||
//Check if element is a String | ||
if(_.isString(element)){ | ||
var resultString = options.handleString ? options.handleString(element, item) : handleString(element, item); | ||
result.push({ | ||
result = [{ | ||
item: item, | ||
value: resultString | ||
}); | ||
value: options.handleString(element, item), | ||
}]; | ||
} | ||
//Check if element is a Number | ||
else if(_.isNumber(element)){ | ||
var resultNumber = options.handleNumber ? options.handleNumber(element, item) : handleNumber(element, item); | ||
result.push({ | ||
result = [{ | ||
item: item, | ||
value: resultNumber | ||
}); | ||
value: options.handleNumber(element, item), | ||
}]; | ||
} | ||
//Check if element is a Boolean | ||
else if(_.isBoolean(element)){ | ||
var resultBoolean = options.handleBoolean ? options.handleBoolean(element, item) : handleBoolean(element, item); | ||
result.push({ | ||
result = [{ | ||
item: item, | ||
value: resultBoolean | ||
}); | ||
value: options.handleBoolean(element, item), | ||
}]; | ||
} | ||
//Check if element is a Date | ||
else if(_.isDate(element)){ | ||
var resultDate = options.handleDate ? options.handleDate(element, item) : handleDate(element, item); | ||
result.push({ | ||
result = [{ | ||
item: item, | ||
value: resultDate | ||
}); | ||
value: options.handleDate(element, item), | ||
}]; | ||
} | ||
@@ -253,11 +236,3 @@ //Check if element is an Array | ||
var resultArray = handleArray(element, item); | ||
resultArray.forEach(function (resultArrayElement) { | ||
if(resultArrayElement.item != null && item){ | ||
resultArrayElement.item = item + options.headerPathString + resultArrayElement.item; | ||
} | ||
if(!resultArrayElement.item && item){ | ||
resultArrayElement.item = item; | ||
} | ||
result.push(resultArrayElement); | ||
}); | ||
result = headers(resultArray, item); | ||
} | ||
@@ -267,11 +242,3 @@ //Check if element is a Object | ||
var resultObject = handleObject(element, item); | ||
resultObject.forEach(function (resultObjectElement) { | ||
if(resultObjectElement.item != null && item) { | ||
resultObjectElement.item = item + options.headerPathString + resultObjectElement.item; | ||
} | ||
if(!resultObjectElement.item && item){ | ||
resultObjectElement.item = item; | ||
} | ||
result.push(resultObjectElement); | ||
}); | ||
result = headers(resultObject, item); | ||
} | ||
@@ -317,3 +284,3 @@ return result; | ||
//Check if there is a firstElement and if the current element dont have a item | ||
if(resultElement.item == null && firstElementWithoutItem != null){ | ||
if(!resultElement.item && firstElementWithoutItem !== undefined){ | ||
//Append the value to the firstElementWithoutItem. | ||
@@ -325,3 +292,3 @@ result[firstElementWithoutItem].value += options.arrayPathString + resultElement.value; | ||
//Set the firstElement if its not set | ||
if(firstElementWithoutItem == null && resultElement.item == null) | ||
if(!firstElementWithoutItem && !resultElement.item) | ||
firstElementWithoutItem = resultIndex; | ||
@@ -328,0 +295,0 @@ //Keep the item in the array |
{ | ||
"name": "jsonexport", | ||
"version": "1.4.2", | ||
"version": "1.5.0", | ||
"description": "Makes easy to convert JSON to CSV", | ||
"main": "./lib", | ||
"scripts": { | ||
"test": "mocha tests/*.js tests/**/*.js" | ||
"test": "mocha tests/*.js tests/**/*.js", | ||
"lint": "./node_modules/.bin/jshint ./lib/index.js" | ||
}, | ||
@@ -41,4 +42,5 @@ "bin": { | ||
"chai": "^3.5.0", | ||
"jshint": "^2.9.4", | ||
"mocha": "^3.1.0" | ||
} | ||
} |
@@ -19,2 +19,4 @@ # jsonexport | ||
---------------------- | ||
- v1.5.0 - escaping content in headers / arrays (papswell) | ||
- v1.4.2 - default date handler return date.toLocaleString (jclay) | ||
- v1.3.2 - fix userOptions optional | ||
@@ -76,7 +78,7 @@ - v1.3.1 - object & array test | ||
``` | ||
name;lastname | ||
Bob;Smith | ||
James;David | ||
Robert;Miller | ||
David;Martin | ||
name,lastname | ||
Bob,Smith | ||
James,David | ||
Robert,Miller | ||
David,Martin | ||
``` | ||
@@ -125,7 +127,7 @@ | ||
``` | ||
lastname;name;family.type;family.name;nickname;location | ||
Smith;Bob;Father;Peter;; | ||
David;James;Mother;Julie;; | ||
Miller;Robert;;;;1231,3214,4214 | ||
Martin;David;;;dmartin; | ||
lastname,name,family.type,family.name,nickname,location | ||
Smith,Bob,Father,Peter,, | ||
David,James,Mother,Julie,, | ||
Miller,Robert,,,,1231,3214,4214 | ||
Martin,David,,,dmartin, | ||
``` | ||
@@ -157,5 +159,5 @@ | ||
``` | ||
cars;12 | ||
roads;5 | ||
traffic;slow | ||
cars,12 | ||
roads,5 | ||
traffic,slow | ||
``` | ||
@@ -191,9 +193,9 @@ | ||
``` | ||
cars;12 | ||
roads;5 | ||
traffic;slow | ||
speed.max;123 | ||
speed.avg;20 | ||
speed.min;5 | ||
size;10,20 | ||
cars,12 | ||
roads,5 | ||
traffic,slow | ||
speed.max,123 | ||
speed.avg,20 | ||
speed.min,5 | ||
size,10;20 | ||
``` | ||
@@ -249,4 +251,4 @@ | ||
``` | ||
lang;Hey - Node.js | ||
module;Hey - jsonexport | ||
lang,Hey - Node.js | ||
module,Hey - jsonexport | ||
``` |
@@ -0,1 +1,5 @@ | ||
/* jshint node:true */ | ||
/* jshint esversion: 6 */ | ||
/* jshint -W030 */ | ||
var chai = require('chai'); | ||
@@ -12,5 +16,6 @@ var expect = chai.expect; | ||
name: 'James', | ||
lastname: 'David' | ||
lastname: 'David', | ||
escaped: 'I am a "quoted" field' | ||
}], {}, (err, csv) => { | ||
expect(csv).to.equal('name,lastname\nBob,Smith\nJames,David'); | ||
expect(csv).to.equal('name,lastname,escaped\nBob,Smith,\nJames,David,I am a ""quoted"" field'); | ||
}); | ||
@@ -17,0 +22,0 @@ }); |
@@ -0,1 +1,5 @@ | ||
/* jshint node:true */ | ||
/* jshint esversion: 6 */ | ||
/* jshint -W030 */ | ||
var chai = require('chai'); | ||
@@ -24,7 +28,8 @@ var expect = chai.expect; | ||
}, | ||
size: [10,20] | ||
size: [10,20], | ||
escaped: 'I am a "quoted" field' | ||
}, {}, (err, csv) => { | ||
expect(csv).to.equal('cars,12\nroads,5\ntraffic,slow\nspeed.max,123\nspeed.avg,20\nspeed.min,5\nsize,10;20'); | ||
expect(csv).to.equal('cars,12\nroads,5\ntraffic,slow\nspeed.max,123\nspeed.avg,20\nspeed.min,5\nsize,10;20\nescaped,I am a ""quoted"" field'); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42411
16
707
249
3