Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding "unwindPath" option that enables data transformation prior to … #140

Merged
merged 2 commits into from
Sep 20, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ try {
- `eol` - String, it gets added to each row of data. Defaults to `` if not specified.
- `newLine` - String, overrides the default OS line ending (i.e. `\n` on Unix and `\r\n` on Windows).
- `flatten` - Boolean, flattens nested JSON using [flat]. Defaults to `false`.
- `unwindPath` - String, creates multiple rows from a single JSON document similar to MongoDB's $unwind
- `excelStrings` - Boolean, converts string data into normalized Excel style data.
- `includeEmptyRows` - Boolean, includes empty rows. Defaults to `false`.
- `callback` - `function (error, csvString) {}`. If provided, will callback asynchronously. Only supported for compatibility reasons.
Expand Down Expand Up @@ -264,7 +265,42 @@ car.make, car.model, price, color
"Porsche", "9PA AF1", 60000, "green"
```

### Example 7

You can unwind arrays similar to MongoDB's $unwind operation using the `unwindPath` option.

```javascript
var json2csv = require('json2csv');
var fs = require('fs');
var fields = ['carModel', 'price', 'colors'];
var myCars = [
{ "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only thing that needs a fixup is the formatting on this array, and the extra spaces between fields.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with quotes. Basically match the other example code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will make the change!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, pushed the change.

{ "carModel": "BMW", "price": 15000, "colors": ["red","blue"] },
{ "carModel": "Mercedes", "price": 20000, "colors": "yellow" },
{ "carModel": "Porsche", "price": 30000, "colors": ["green","teal","aqua"] }
];
var csv = json2csv({ data: myCars, fields: fields, unwindPath: 'colors' });

fs.writeFile('file.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
```

The content of the "file.csv" should be

```
"carModel","price","colors"
"Audi",0,"blue"
"Audi",0,"green"
"Audi",0,"yellow"
"BMW",15000,"red"
"BMW",15000,"blue"
"Mercedes",20000,"yellow"
"Porsche",30000,"green"
"Porsche",30000,"teal"
"Porsche",30000,"aqua"
```

## Command Line Interface

Expand Down
55 changes: 51 additions & 4 deletions lib/json2csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,34 @@ var os = require('os');
var lodashGet = require('lodash.get');
var lodashFlatten = require('lodash.flatten');
var lodashUniq = require('lodash.uniq');
var lodashSet = require('lodash.set');
var lodashCloneDeep = require('lodash.clonedeep');
var flatten = require('flat');

/**
* @name Json2CsvParams
* @type {Object}
* @property {Array} data - array of JSON objects
* @property {Array} fields - see documentation for details
* @property {String[]} [fieldNames] - names for fields at the same indexes. Must be same length as fields array
* (Optional. Maintained for backwards compatibility. Use fields config object for more features)
* @property {String} [del=","] - delimiter of columns
* @property {String} [defaultValue="<empty>"] - default value to use when missing data
* @property {String} [quotes='"'] - quotes around cell values and column names
* @property {String} [doubleQuotes='"""'] - the value to replace double quotes in strings
* @property {Boolean} [hasCSVColumnTitle=true] - determines whether or not CSV file will contain a title column
* @property {String} [eol=''] - it gets added to each row of data
* @property {String} [newLine] - overrides the default OS line ending (\n on Unix \r\n on Windows)
* @property {Boolean} [flatten=false] - flattens nested JSON using flat (https://www.npmjs.com/package/flat)
* @property {String} [unwindPath] - similar to MongoDB's $unwind, Deconstructs an array field from the input JSON to output a row for each element
* @property {Boolean} [excelStrings] - converts string data into normalized Excel style data
* @property {Boolean} [includeEmptyRows=false] - includes empty rows
*/

/**
* Main function that converts json to csv.
*
* @param {Object} params Function parameters containing data, fields,
* @param {Json2CsvParams} params Function parameters containing data, fields,
* delimiter (default is ','), hasCSVColumnTitle (default is true)
* and default value (default is '')
* @param {Function} [callback] Callback function
Expand Down Expand Up @@ -51,7 +73,7 @@ module.exports = function (params, callback) {
*
* Note that this modifies params.
*
* @param {Object} params Function parameters containing data, fields,
* @param {Json2CsvParams} params Function parameters containing data, fields,
* delimiter, default value, mark quotes and hasCSVColumnTitle
*/
function checkParams(params) {
Expand Down Expand Up @@ -120,7 +142,7 @@ function checkParams(params) {
/**
* Create the title row with all the provided fields as column headings
*
* @param {Object} params Function parameters containing data, fields and delimiter
* @param {Json2CsvParams} params Function parameters containing data, fields and delimiter
* @returns {String} titles as a string
*/
function createColumnTitles(params) {
Expand Down Expand Up @@ -172,7 +194,8 @@ function replaceQuotationMarks(stringifiedElement, quotes) {
* @returns {String} csv string
*/
function createColumnContent(params, str) {
params.data.forEach(function (dataElement) {
var dataRows = createDataRows(params);
dataRows.forEach(function (dataElement) {
//if null do nothing, if empty object without includeEmptyRows do nothing
if (dataElement && (Object.getOwnPropertyNames(dataElement).length > 0 || params.includeEmptyRows)) {
var line = '';
Expand Down Expand Up @@ -243,3 +266,27 @@ function createColumnContent(params, str) {

return str;
}

/**
* Performs the unwind logic if necessary to convert single JSON document into multiple rows
* @param params
*/
function createDataRows(params) {
var dataRows = params.data;
if (params.unwindPath) {
dataRows = [];
params.data.forEach(function(dataEl) {
var unwindArray = lodashGet(dataEl, params.unwindPath);
if (Array.isArray(unwindArray)) {
unwindArray.forEach(function(unwindEl) {
var dataCopy = lodashCloneDeep(dataEl);
lodashSet(dataCopy, params.unwindPath, unwindEl);
dataRows.push(dataCopy);
});
} else {
dataRows.push(dataEl);
}
});
}
return dataRows;
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"flat": "^2.0.0",
"lodash.flatten": "^4.2.0",
"lodash.get": "^4.3.0",
"lodash.set": "^4.3.0",
"lodash.uniq": "^4.3.0",
"lodash.clonedeep": "^4.3.0",
"path-is-absolute": "^1.0.0"
},
"devDependencies": {
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/csv/unwind.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"carModel","price","colors"
"Audi",0,"blue"
"Audi",0,"green"
"Audi",0,"yellow"
"BMW",15000,"red"
"BMW",15000,"blue"
"Mercedes",20000,"yellow"
"Porsche",30000,"green"
"Porsche",30000,"teal"
"Porsche",30000,"aqua"
6 changes: 6 additions & 0 deletions test/fixtures/json/unwind.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{ "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] },
{ "carModel": "BMW", "price": 15000, "colors": ["red","blue"] },
{ "carModel": "Mercedes", "price": 20000, "colors": "yellow" },
{ "carModel": "Porsche", "price": 30000, "colors": ["green","teal","aqua"] }
]
1 change: 1 addition & 0 deletions test/helpers/load-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var fixtures = [
'emptyRow',
'emptyRowNotIncluded',
'emptyRowDefaultValues',
'unwind'
];

/*eslint-disable no-console*/
Expand Down
25 changes: 19 additions & 6 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var jsonDefaultValueEmpty = require('./fixtures/json/defaultValueEmpty');
var jsonTrailingBackslash = require('./fixtures/json/trailingBackslash');
var jsonOverriddenDefaultValue = require('./fixtures/json/overridenDefaultValue');
var jsonEmptyRow = require('./fixtures/json/emptyRow');
var jsonUnwind = require('./fixtures/json/unwind');
var csvFixtures = {};

async.parallel(loadFixtures(csvFixtures), function (err) {
Expand Down Expand Up @@ -366,12 +367,12 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
label: 'NEST1',
value: 'bird.nest1'
},
'bird.nest2',
{
label: 'nonexistent',
value: 'fake.path',
default: 'col specific default value'
}
'bird.nest2',
{
label: 'nonexistent',
value: 'fake.path',
default: 'col specific default value'
}
],
defaultValue: 'NULL'
}, function (error, csv) {
Expand Down Expand Up @@ -590,4 +591,16 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
t.end();
});
});

test('should unwind an array into multiple rows', function(t) {
json2csv({
data: jsonUnwind,
fields: ['carModel', 'price', 'colors'],
unwindPath: 'colors'
}, function(error, csv) {
t.error(error);
t.equal(csv, csvFixtures.unwind);
t.end()
})
});
});