Skip to content

Commit 7e87010

Browse files
committed
Added comments to json-2-csv; Updated tests
1 parent e1047d9 commit 7e87010

File tree

3 files changed

+51
-36
lines changed

3 files changed

+51
-36
lines changed

lib/json-2-csv.js

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,73 +3,88 @@
33
var _ = require('underscore'),
44
async = require('async');
55

6-
var options = {};
6+
var options = {}; // Initialize the options - this will be populated when the csv2json function is called.
77

88
// Takes the parent heading and this doc's data and creates the subdocument headings (string)
99
var retrieveSubHeading = function (heading, data) {
10-
var subKeys = _.keys(data),
11-
newKey;
10+
var subKeys = _.keys(data), // retrieve the keys from the current document
11+
newKey; // temporary variable to aid in determining the heading - used to generate the 'nested' headings
1212
_.each(subKeys, function (subKey, indx) {
13-
newKey = heading === '' ? subKey : heading + '.' + subKey;
14-
if (typeof data[subKey] === 'object' && data[subKey] !== null) { // Another nested document
15-
subKeys[indx] = retrieveSubHeading(newKey, data[subKey]);
13+
// If the given heading is empty, then we set the heading to be the subKey, otherwise set it as a nested heading w/ a dot
14+
newKey = heading === '' ? subKey : heading + '.' + subKey;
15+
if (typeof data[subKey] === 'object' && data[subKey] !== null) { // If we have another nested document
16+
subKeys[indx] = retrieveSubHeading(newKey, data[subKey]); // Recur on the subdocument to retrieve the full key name
1617
} else {
17-
subKeys[indx] = newKey;
18+
subKeys[indx] = newKey; // Set the key name since we don't have a sub document
1819
}
1920
});
20-
return subKeys.join(options.DELIMITER);
21+
return subKeys.join(options.DELIMITER); // Return the headings joined by our delimiter
2122
};
2223

24+
// Retrieve the headings for all documents and return it. This checks that all documents have the same schema.
2325
var retrieveHeading = function (data) {
24-
return function (cb) {
25-
var keys = _.keys(data);
26-
_.each(keys, function (key, indx) {
27-
if (typeof data[key] === 'object') {
26+
return function (cb) { // Returns a function that takes a callback - the function is passed to async.parallel
27+
var keys = _.keys(data); // Retrieve the current data keys
28+
_.each(keys, function (key, indx) { // for each key
29+
if (typeof data[key] === 'object') {
30+
// if the data at the key is a document, then we retrieve the subHeading starting with an empty string heading and the doc
2831
keys[indx] = retrieveSubHeading('', data[key]);
2932
}
3033
});
34+
// Retrieve the unique array of headings (keys)
3135
keys = _.uniq(keys);
36+
// If we have more than 1 unique list, then not all docs have the same schema - report an error
3237
if (keys.length > 1) { throw new Error('Not all documents have the same schema.', keys); }
33-
cb(null, _.flatten(keys).join(options.DELIMITER));
38+
cb(null, _.flatten(keys).join(options.DELIMITER)); // Return headings back
3439
};
3540
};
3641

42+
// Convert the given data with the given keys
3743
var convertData = function (data, keys) {
38-
var output = [];
39-
_.each(keys, function (key, indx) {
40-
var value = data[key];
41-
if (keys.indexOf(key) > -1) {
42-
if (typeof value === 'object') {
43-
output.push(convertData(value, _.keys(value)));
44+
var output = [], // Array of CSV representing converted docs
45+
value; // Temporary variable to store the current data
46+
_.each(keys, function (key, indx) { // For each key
47+
value = data[key]; // Set the current data that we are looking at
48+
if (keys.indexOf(key) > -1) { // If the keys contain the current key, then process the data
49+
if (typeof value === 'object') { // If we have an object
50+
output.push(convertData(value, _.keys(value))); // Push the recursively generated CSV
4451
} else {
45-
output.push(value);
52+
output.push(value); // Otherwise push the current value
4653
}
4754
}
4855
});
49-
return output.join(options.DELIMITER);
56+
return output.join(options.DELIMITER); // Return the data joined by our field delimiter
5057
};
5158

59+
// Generate the CSV representing the given data.
5260
var generateCsv = function (data) {
53-
return function (cb) {
61+
return function (cb) { // Returns a function that takes a callback - the function is passed to async.parallel
62+
// Reduce each JSON document in data to a CSV string and append it to the CSV accumulator
5463
return cb(null, _.reduce(data, function (csv, doc) { return csv += convertData(doc, _.keys(doc)) + options.EOL; }, ''));
5564
};
5665
};
5766

5867
module.exports = {
5968

69+
// Function to export internally
70+
// Takes options as a document, data as a JSON document array, and a callback that will be used to report the results
6071
json2csv: function (opts, data, callback) {
61-
if (!callback) { throw new Error('A callback is required!'); }
72+
if (!callback) { throw new Error('A callback is required!'); } // If a callback wasn't provided, throw an error
6273
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; }
65-
if (typeof data === 'object' && !data.length) { // Single document, not an array
74+
else { options = opts; } // Options were passed, set the global options value
75+
if (!data) { callback(new Error('Cannot call json2csv on ' + data + '.')); return null; } // If we don't receive data, report an error
76+
if (typeof data !== 'object') { // If the data was not a single document or an array of documents
77+
cb(new Error('Data provided was not an array of documents.')); // Report the error back to the caller
78+
} else if (typeof data === 'object' && !data.length) { // Single document, not an array
6679
data = [data]; // Convert to an array of the given document
6780
}
81+
// Retrieve the heading and the CSV asynchronously in parallel
6882
async.parallel([retrieveHeading(data), generateCsv(data)], function (err, res) {
6983
if (!err) {
70-
callback(null, res.join(options.EOL));
84+
// Data received with no errors, join the two responses with an end of line delimiter to setup heading and CSV body
85+
callback(null, res.join(options.EOL));
7186
} else {
72-
callback(err, null);
87+
callback(err, null); // Report received error back to caller
7388
}
7489
});
7590
}

test/testComma.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ var json2csvTests = function () {
7676

7777
it('should throw an error about not having been passed data - 1', function (done) {
7878
converter.json2csv(null, function (err, csv) {
79-
err.message.should.equal('Cannot call json2csv on null');
79+
err.message.should.equal('Cannot call json2csv on null.');
8080
done();
8181
}, options);
8282
});
8383

8484
it('should throw an error about not having been passed data - 2', function (done) {
8585
converter.json2csv(undefined, function (err, csv) {
86-
err.message.should.equal('Cannot call json2csv on undefined');
86+
err.message.should.equal('Cannot call json2csv on undefined.');
8787
done();
8888
}, options);
8989
});
@@ -176,14 +176,14 @@ var json2csvTests = function () {
176176

177177
it('should throw an error about not having been passed data - 1', function (done) {
178178
converter.json2csv(null, function (err, csv) {
179-
err.message.should.equal('Cannot call json2csv on null');
179+
err.message.should.equal('Cannot call json2csv on null.');
180180
done();
181181
});
182182
});
183183

184184
it('should throw an error about not having been passed data - 2', function (done) {
185185
converter.json2csv(undefined, function (err, csv) {
186-
err.message.should.equal('Cannot call json2csv on undefined');
186+
err.message.should.equal('Cannot call json2csv on undefined.');
187187
done();
188188
});
189189
});

test/testSemi.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ var json2csvTests = function () {
7777

7878
it('should throw an error about not having been passed data - 1', function (done) {
7979
converter.json2csv(null, function (err, csv) {
80-
err.message.should.equal('Cannot call json2csv on null');
80+
err.message.should.equal('Cannot call json2csv on null.');
8181
done();
8282
}, options);
8383
});
8484

8585
it('should throw an error about not having been passed data - 2', function (done) {
8686
converter.json2csv(undefined, function (err, csv) {
87-
err.message.should.equal('Cannot call json2csv on undefined');
87+
err.message.should.equal('Cannot call json2csv on undefined.');
8888
done();
8989
}, options);
9090
});
@@ -178,14 +178,14 @@ var json2csvTests = function () {
178178

179179
it('should throw an error about not having been passed data - 1', function (done) {
180180
converter.json2csv(null, function (err, csv) {
181-
err.message.should.equal('Cannot call json2csv on null');
181+
err.message.should.equal('Cannot call json2csv on null.');
182182
done();
183183
});
184184
});
185185

186186
it('should throw an error about not having been passed data - 2', function (done) {
187187
converter.json2csv(undefined, function (err, csv) {
188-
err.message.should.equal('Cannot call json2csv on undefined');
188+
err.message.should.equal('Cannot call json2csv on undefined.');
189189
done();
190190
});
191191
});

0 commit comments

Comments
 (0)