Skip to content

Commit

Permalink
Convert lib to synchronous operation. (#126)
Browse files Browse the repository at this point in the history
* Convert lib to synchronous operation.

Optional callback is still functional but uses process.nextTick()
to avoid accidentally-synchronous operation.

Previous versions had an async API but actually operated
synchronously.

* Update README and bin for synchronous operation.

* Update typescript definition for synchronous operation.
  • Loading branch information
STRML authored and Ilya Radchenko committed Jul 7, 2016
1 parent eb0bb6e commit b91158b
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 93 deletions.
52 changes: 22 additions & 30 deletions README.md
Expand Up @@ -23,10 +23,13 @@ Include the module and run or [use it from the Command Line](https://github.com/
var json2csv = require('json2csv');
var fields = ['field1', 'field2', 'field3'];

json2csv({ data: myData, fields: fields }, function(err, csv) {
if (err) console.log(err);
console.log(csv);
});
try {
console.log(json2csv({ data: myData, fields: fields }));
} catch (err) {
// Errors are thrown for bad options, or if the data is empty and no fields are provided.
// Be sure to provide fields if it is possible that your data array will be empty.
console.error(err);
}
```

## Features
Expand Down Expand Up @@ -62,7 +65,7 @@ json2csv({ data: myData, fields: fields }, function(err, csv) {
- `flatten` - Boolean, flattens nested JSON using [flat]. Defaults to `false`.
- `excelStrings` - Boolean, converts string data into normalized Excel style data.
- `includeEmptyRows` - Boolean, includes empty rows. Defaults to `false`.
- `callback` - **Required**; `function (error, csvString) {}`. To create a promise, you can use `var toCSV = Bluebird.promisify(json2csv)`, see [Bluebird] docs.
- `callback` - `function (error, csvString) {}`. If provided, will callback asynchronously. Only supported for compatibility reasons.

#### Example `fields` option
``` javascript
Expand Down Expand Up @@ -113,12 +116,10 @@ var myCars = [
}
];

json2csv({ data: myCars, fields: fields }, function(err, csv) {
if (err) console.log(err);
fs.writeFile('file.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
var csv = json2csv({ data: myCars, fields: fields });
fs.writeFile('file.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
```

Expand Down Expand Up @@ -162,10 +163,8 @@ Use a custom delimiter to create tsv files. Add it as the value of the del prope
var json2csv = require('json2csv');
var fields = ['car', 'price', 'color'];

json2csv({ data: myCars, fields: fields, del: '\t' }, function(err, tsv) {
if (err) console.log(err);
console.log(tsv);
});
var tsv = json2csv({ data: myCars, fields: fields, del: '\t' });
console.log(tsv);
```

Will output:
Expand All @@ -189,10 +188,8 @@ var json2csv = require('json2csv');
var fields = ['car', 'price'];
var fieldNames = ['Car Name', 'Price USD'];

json2csv({ data: myCars, fields: fields, fieldNames: fieldNames }, function(err, csv) {
if (err) console.log(err);
console.log(csv);
});
var csv = json2csv({ data: myCars, fields: fields, fieldNames: fieldNames });
console.log(csv);
```

### Example 5
Expand All @@ -210,10 +207,8 @@ var opts = {
quotes: ''
};

json2csv(opts, function(err, csv) {
if (err) console.log(err);
console.log(csv);
});
var csv = json2csv(opts);
console.log(csv);
```

Results in
Expand Down Expand Up @@ -249,12 +244,10 @@ var myCars = [
}
];

json2csv({ data: myCars, fields: fields }, function(err, csv) {
if (err) console.log(err);
fs.writeFile('file.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
var csv = json2csv({ data: myCars, fields: fields });
fs.writeFile('file.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
```

Expand Down Expand Up @@ -435,5 +428,4 @@ See [LICENSE.md](LICENSE.md).
[dev-badge]: https://david-dm.org/zemirco/json2csv.svg
[dev-badge-url]: https://david-dm.org/zemirco/json2csv
[CHANGELOG]: CHANGELOG.md
[Bluebird]: http://bluebirdjs.com/docs/api/promise.promisify.html
[flat]: https://www.npmjs.com/package/flat
48 changes: 19 additions & 29 deletions bin/json2csv.js
Expand Up @@ -13,8 +13,8 @@ var pkg = require('../package');

program
.version(pkg.version)
.option('-i, --input <input>', 'Path and name of the incoming json file.')
.option('-o, --output [output]', 'Path and name of the resulting csv file. Defaults to console.')
.option('-i, --input <input>', 'Path and name of the incoming json file. If not provided, will read from stdin.')
.option('-o, --output [output]', 'Path and name of the resulting csv file. Defaults to stdout.')
.option('-f, --fields <fields>', 'Specify the fields to convert.')
.option('-l, --fieldList [list]', 'Specify a file with a list of fields to include. One field per line.')
.option('-d, --delimiter [delimiter]', 'Specify a delimiter other than the default comma to use.')
Expand Down Expand Up @@ -79,7 +79,7 @@ function getInput(callback) {
});
}

function logPretty(csv, callback) {
function logPretty(csv) {
var lines = csv.split(os.EOL);
var table = new Table({
head: lines[0].split(','),
Expand All @@ -90,11 +90,8 @@ function logPretty(csv, callback) {

for (var i = 1; i < lines.length; i++) {
table.push(lines[i].split('","'));

if (i === lines.length - 1) {
callback(table.toString());
}
}
return table.toString();
}

getFields(function (err, fields) {
Expand Down Expand Up @@ -129,30 +126,23 @@ getFields(function (err, fields) {
opts.newLine = program.newLine;
}

json2csv(opts, function (csvError, csv) {
if (csvError) {
debug(csvError);
}

if (program.output) {
fs.writeFile(program.output, csv, function (writeError) {
if (writeError) {
throw new Error('Cannot save to ' + program.output + ': ' + writeError);
}
var csv = json2csv(opts);
if (program.output) {
fs.writeFile(program.output, csv, function (writeError) {
if (writeError) {
throw new Error('Cannot save to ' + program.output + ': ' + writeError);
}

debug(program.input + ' successfully converted to ' + program.output);
});
debug(program.input + ' successfully converted to ' + program.output);
});
} else {
/*eslint-disable no-console */
if (program.pretty) {
console.log(logPretty(csv));
} else {
/*eslint-disable no-console */
if (program.pretty) {
logPretty(csv, function (res) {
console.log(res);
});
} else {
console.log(csv);
}
/*eslint-enable no-console */
console.log(csv);
}
});
/*eslint-enable no-console */
}
});
});
3 changes: 2 additions & 1 deletion index.d.ts
Expand Up @@ -25,7 +25,8 @@ declare namespace json2csv {
(error: Error, csv: string): void;
}

export function json2csv(options: IOptions, callback: ICallback): string;
export function json2csv(options: IOptions, callback: ICallback): void;
export function json2csv(options: IOptions): string;
}

export = json2csv.json2csv;
54 changes: 31 additions & 23 deletions lib/json2csv.js
Expand Up @@ -6,41 +6,51 @@ var lodashGet = require('lodash.get');
var flatten = require('flat');

/**
* Main function that converts json to csv
* Main function that converts json to csv.
*
* @param {Object} params Function parameters containing data, fields,
* delimiter (default is ','), hasCSVColumnTitle (default is true)
* and default value (default is '')
* @param {Function} callback(err, csv) - Callback function
* @param {Function} [callback] Callback function
* if error, returning error in call back.
* if csv is created successfully, returning csv output to callback.
*/
module.exports = function (params, callback) {
if (!callback || typeof callback !== 'function') {
throw new Error('Callback is required');
}

checkParams(params, function (err) {
if (err) {
return callback(err);
var hasCallback = typeof callback === 'function';

var err;
try {
checkParams(params);
} catch (err) {
if (hasCallback) {
return process.nextTick(function () {
callback(err);
});
} else {
throw err;
}

var titles = createColumnTitles(params);
var csv = createColumnContent(params, titles);

callback(null, csv);
});
}
var titles = createColumnTitles(params);
var csv = createColumnContent(params, titles);
if (hasCallback) {
return process.nextTick(function () {
callback(null, csv);
});
} else {
return csv;
}
};


/**
* Check passing params
* Check passing params.
*
* Note that this modifies params.
*
* @param {Object} params Function parameters containing data, fields,
* delimiter, default value, mark quotes and hasCSVColumnTitle
* @param {Function} callback Callback function returning error when invalid field is found
*/
function checkParams(params, callback) {
function checkParams(params) {
params.data = params.data || [];

// if data is an Object, not in array [{}], then just create 1 item array.
Expand All @@ -55,14 +65,14 @@ function checkParams(params, callback) {

// Set params.fields default to first data element's keys
if (!params.fields && (params.data.length === 0 || typeof params.data[0] !== 'object')) {
return callback(new Error('params should include "fields" and/or non-empty "data" array of objects'));
throw new Error('params should include "fields" and/or non-empty "data" array of objects');
}
params.fields = params.fields || Object.keys(params.data[0]);


//#check fieldNames
if (params.fieldNames && params.fieldNames.length !== params.fields.length) {
return callback(new Error('fieldNames and fields should be of the same length, if fieldNames is provided.'));
throw new Error('fieldNames and fields should be of the same length, if fieldNames is provided.');
}

// Get fieldNames from fields
Expand Down Expand Up @@ -93,8 +103,6 @@ function checkParams(params, callback) {

//#check include empty rows, defaults to false
params.includeEmptyRows = params.includeEmptyRows || false;

callback(null);
}

/**
Expand Down Expand Up @@ -188,7 +196,7 @@ function createColumnContent(params, str) {
//JSON.stringify('\\') results in a string with two backslash
//characters in it. I.e. '\\\\'.
stringifiedElement = stringifiedElement.replace(/\\\\/g, '\\');

if (params.excelStrings && typeof val === 'string') {
stringifiedElement = '"="' + stringifiedElement + '""';
}
Expand Down
47 changes: 37 additions & 10 deletions test/index.js
Expand Up @@ -22,16 +22,44 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
/*eslint-enable no-console*/
}

test('should throw if no callback', function (t) {
t.throws(function () {
json2csv({
data: jsonDefault
});
}, /Callback is required/);
test('should work synchronously', function (t) {
var csv = json2csv({
data: jsonDefault
});
t.equal(csv, csvFixtures.default);
t.end();
});

test('should error if fieldNames don\'t line up to fields', function (t) {
test('should work asynchronously and not release zalgo', function (t) {
var releasedZalgo = true;
json2csv({
data: jsonDefault
}, function (err, csv) {
t.equal(csv, csvFixtures.default);
t.notOk(releasedZalgo);
t.end();
});

releasedZalgo = false;
});

test('should error synchronously if fieldNames don\'t line up to fields', function (t) {
var csv;
try {
csv = json2csv({
data: jsonDefault,
field: ['carModel'],
fieldNames: ['test', 'blah']
});
t.notOk(true);
} catch (error) {
t.equal(error.message, 'fieldNames and fields should be of the same length, if fieldNames is provided.');
t.notOk(csv);
t.end();
}
});

test('should error asynchronously if fieldNames don\'t line up to fields', function (t) {
json2csv({
data: jsonDefault,
field: ['carModel'],
Expand All @@ -53,7 +81,6 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
t.end();
});
});


test('should parse json to csv without fields', function (t) {
json2csv({
Expand Down Expand Up @@ -364,7 +391,7 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
t.end();
});
});

test('should escape " when preceeded by \\', function (t){
json2csv({
data: [{field: '\\"'}]
Expand All @@ -374,7 +401,7 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
t.end();
});
});

test('should format strings to force excel to view the values as strings', function (t) {
json2csv({
data: jsonDefault,
Expand Down

0 comments on commit b91158b

Please sign in to comment.