Skip to content

Commit 9bdf8a5

Browse files
committed
Added ~95 tests. Fixed bugs in converter. Updated to v0.1.2. Updated README. Allows custom options.
1 parent f98b1a3 commit 9bdf8a5

17 files changed

+1130
-206
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ var converter = require('json-2-csv');
2020

2121
### API
2222

23-
#### json2csv(array, callback)
23+
#### json2csv(array, callback, options)
2424

2525
* `array` - An array of JSON documents
2626
* `callback` - A function of the form `function (err, csv)`; This function will receive any errors and/or the CSV generated.
27+
* `options` - (Optional) A JSON document specifying any of {`DELIMITER`, `EOL`, `PARSE_CSV_NUMBERS`}
28+
** `DELIMITER` - String - Field Delimiter. Default: `','`
29+
** `EOL` - String - End of Line Delimiter. Default: `'\n'`
30+
** `PARSE_CSV_NUMBERS` - Boolean - Should numbers that are found in the CSV be converted to numbers? Default: `false`
2731

2832
##### json2csv Example:
2933

@@ -69,10 +73,14 @@ Nissan,Murano,2013,7106,S AWD
6973
BMW,X5,2014,3287,M
7074
```
7175

72-
#### csv2json(csv, callback)
76+
#### csv2json(csv, callback, options)
7377

7478
* `csv` - A string of CSV
7579
* `callback` - A function of the form `function (err, array)`; This function will receive any errors and/or the array of JSON documents generated.
80+
* `options` - (Optional) A JSON document specifying any of {`DELIMITER`, `EOL`, `PARSE_CSV_NUMBERS`}
81+
** `DELIMITER` - String - Field Delimiter. Default: `','`
82+
** `EOL` - String - End of Line Delimiter. Default: `'\n'`
83+
** `PARSE_CSV_NUMBERS` - Boolean - Should numbers that are found in the CSV be converted to numbers? Default: `false`
7684

7785
##### csv2json Example:
7886

@@ -108,29 +116,28 @@ object
108116
Specifications: { Mileage: '3287', Trim: 'M' } } ]
109117
```
110118

111-
112119
## Tests
113120

114121
```bash
115122
$ npm test
116123
```
117124

118-
_Note_: This requires `mocha`, `should`, and `async`.
125+
_Note_: This requires `mocha`, `should`, `async`, and `underscore`.
119126

120127
## Features
121128

122129
- Header Generation (per document keys)
123130
- Verifies all documents have same schema
124131
- Supports sub-documents natively
125132
- Custom ordering of columns (see F.A.Q. for more information)
133+
- Ability to re-generate the JSON documents that were used to generate the CSV (including nested documents)
134+
- Allows for custom field delimiters, end of line delimiters, etc.
126135

127136
## F.A.Q.
128137

129138
- Can the order of the keys be changed in the output?
130139
__Yes.__ Currently, changing the order of the keys in the JSON document will also change the order of the columns. (Node 10.26)
131140

132141
## TODO
133-
- Add more tests for both json2csv and csv2json
134-
- Test errors
135142
- Add more comments to code
136-
- Add ability to pass/set options (delimiter, newline, parseInt, etc)
143+
- Use PARSE_CSV_NUMBERS option to actually convert numbers. Not currently implemented.

bin/json-2-csv

Lines changed: 0 additions & 5 deletions
This file was deleted.

lib/converter.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
'use strict';
22

33
var json2Csv = require('./json-2-csv'),
4-
csv2Json = require('./csv-2-json');
4+
csv2Json = require('./csv-2-json'),
5+
_ = require('underscore');
6+
7+
var OPTIONS = function () { // Ensures fields don't change from our defaults
8+
return {
9+
DELIMITER : ',',
10+
EOL : '\n',
11+
PARSE_CSV_NUMBERS : false
12+
};
13+
}
14+
15+
var buildOptions = function (opts) {
16+
var out = _.extend(OPTIONS(), {});
17+
if (!opts) { return out; } // If undefined or null, return defaults
18+
_.each(_.keys(opts), function (key) {
19+
if (out[key]) { // If key is valid, set it
20+
out[key] = opts[key];
21+
} // Else ignore its value
22+
});
23+
return out; // Return customized version
24+
};
525

626
module.exports = {
727

8-
json2csv: function (data, callback) {
9-
json2Csv.json2csv(data, callback);
28+
json2csv: function (array, callback, opts) {
29+
if (!opts) { opts = OPTIONS(); }
30+
else { opts = buildOptions(opts); }
31+
json2Csv.json2csv(opts, array, callback);
1032
},
1133

12-
csv2json: function (data, callback) {
13-
csv2Json.csv2json(data, callback);
34+
csv2json: function (csv, callback, opts) {
35+
if (!opts) { opts = OPTIONS(); }
36+
else { opts = buildOptions(opts); }
37+
csv2Json.csv2json(opts, csv, callback);
1438
}
1539

1640
};

lib/csv-2-json.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
var _ = require('underscore'),
44
async = require('async');
55

6-
var DELIMITER = ',',
7-
EOL = '\n';
6+
var options = {};
87

98
var retrieveHeading = function (lines, callback) {
109
if (!lines.length) {
1110
callback(new Error("No data provided to retrieve heading.")); // Error - no data
1211
}
1312
var heading = lines[0]; // Grab the top line (header line)
14-
return heading.split(DELIMITER); // Return the heading split by the field delimiter
13+
return heading.split(options.DELIMITER); // Return the heading split by the field options.DELIMITER
1514
};
1615

1716
var addNestedKey = function (key, value, doc) {
@@ -37,17 +36,18 @@ var doesKeyExist = function (key, doc) {
3736

3837
var createDoc = function (keys, line, callback) {
3938
var doc = {},
40-
line = line.trim().split(DELIMITER);
39+
val,
40+
line = line.trim().split(options.DELIMITER);
4141
if (line == '') { return false; }
4242
if (keys.length !== line.length) {
4343
callback(new Error("Not every line has a correct number of values.")); // This line doesn't have the same # vals as the header
4444
}
4545
_.each(keys, function (key, indx) {
46-
// TODO: check for case where some or all of nested key path already exists
46+
val = line[indx] === '' ? null : line[indx];
4747
if (key.indexOf('.')) { // Key has '.' representing nested document
48-
doc = addNestedKey(key, line[indx], doc); // Update the doc
48+
doc = addNestedKey(key, val, doc); // Update the doc
4949
} else {
50-
doc[key] = line[indx];
50+
doc[key] = val;
5151
}
5252
});
5353
return doc;
@@ -65,13 +65,15 @@ var convertCSV = function (lines, callback) {
6565

6666
module.exports = {
6767

68-
csv2json: function (data, callback) {
69-
if (!callback) { callback(new Error('A callback is required!')); }
70-
if (!data) { callback(new Error('Cannot call csv2json on ' + data)); }
68+
csv2json: function (opts, data, callback) {
69+
if (!callback) { throw new Error('A callback is required!'); }
70+
if (!opts) { callback(new Error('Options were not passed and are required.')); return null; } // Shouldn't happen, but just in case
71+
else { options = opts; }
72+
if (!data) { callback(new Error('Cannot call csv2json on ' + data)); return null; }
7173
if (typeof data !== 'string') { // CSV is not a string
7274
callback(new Error("CSV is not a string"));
7375
}
74-
var lines = data.split(EOL);
76+
var lines = data.split(options.EOL);
7577
var json = convertCSV(lines, callback);
7678
callback(null, json);
7779
}

lib/json-2-csv.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@
33
var _ = require('underscore'),
44
async = require('async');
55

6-
var DELIMITER = ',',
7-
EOL = '\n';
6+
var options = {};
87

98
// Takes the parent heading and this doc's data and creates the subdocument headings (string)
109
var retrieveSubHeading = function (heading, data) {
1110
var subKeys = _.keys(data),
1211
newKey;
1312
_.each(subKeys, function (subKey, indx) {
1413
newKey = heading === '' ? subKey : heading + '.' + subKey;
15-
if (typeof data[subKey] === 'object') { // Another nested document
14+
if (typeof data[subKey] === 'object' && data[subKey] !== null) { // Another nested document
1615
subKeys[indx] = retrieveSubHeading(newKey, data[subKey]);
1716
} else {
1817
subKeys[indx] = newKey;
1918
}
2019
});
21-
return subKeys.join(DELIMITER);
20+
return subKeys.join(options.DELIMITER);
2221
};
2322

2423
var retrieveHeading = function (data) {
@@ -31,7 +30,7 @@ var retrieveHeading = function (data) {
3130
});
3231
keys = _.uniq(keys);
3332
if (keys.length > 1) { throw new Error('Not all documents have the same schema.', keys); }
34-
cb(null, _.flatten(keys).join(DELIMITER));
33+
cb(null, _.flatten(keys).join(options.DELIMITER));
3534
};
3635
};
3736

@@ -47,26 +46,28 @@ var convertData = function (data, keys) {
4746
}
4847
}
4948
});
50-
return output.join(DELIMITER);
49+
return output.join(options.DELIMITER);
5150
};
5251

5352
var generateCsv = function (data) {
5453
return function (cb) {
55-
return cb(null, _.reduce(data, function (csv, doc) { return csv += convertData(doc, _.keys(doc)) + EOL; }, ''));
54+
return cb(null, _.reduce(data, function (csv, doc) { return csv += convertData(doc, _.keys(doc)) + options.EOL; }, ''));
5655
};
5756
};
5857

5958
module.exports = {
6059

61-
json2csv: function (data, callback) {
60+
json2csv: function (opts, data, callback) {
6261
if (!callback) { throw new Error('A callback is required!'); }
63-
if (!data) { throw new Error('Cannot call json2csv on ' + data); }
62+
if (!opts) { callback(new Error('Options were not passed and are required.')); return null; } // Shouldn't happen, but just in case
63+
else { options = opts; }
64+
if (!data) { callback(new Error('Cannot call json2csv on ' + data)); return null; }
6465
if (typeof data === 'object' && !data.length) { // Single document, not an array
6566
data = [data]; // Convert to an array of the given document
6667
}
6768
async.parallel([retrieveHeading(data), generateCsv(data)], function (err, res) {
6869
if (!err) {
69-
callback(null, res.join(EOL));
70+
callback(null, res.join(options.EOL));
7071
} else {
7172
callback(err, null);
7273
}

lib/testing.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var t = require('./converter');
2+
3+
t.json2csv({
4+
'name': 'mrodrig',
5+
'email': null,
6+
'country': 'USA',
7+
'githubUsername': 'mrodrig'
8+
}, function (err, csv) {
9+
if (err) throw err;
10+
console.log(csv);
11+
}
12+
);

test/CSV/npm-debug.log

Lines changed: 0 additions & 19 deletions
This file was deleted.

test/CSV/singleDoc.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name,email,country,githubUsername
2+
mrodrig,,USA,mrodrig

test/CSV/todo.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/JSON/nestedJson.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
"max": "31000"
2828
}
2929
}
30-
]
30+
]

0 commit comments

Comments
 (0)