|
3 | 3 | var _ = require('underscore'),
|
4 | 4 | async = require('async');
|
5 | 5 |
|
6 |
| -var options = {}; // Initialize the options - this will be populated when the csv2json function is called. |
7 |
| - |
8 |
| -// Takes the parent heading and this doc's data and creates the subdocument headings (string) |
9 |
| -var retrieveSubHeading = function (heading, data) { |
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 |
12 |
| - _.each(subKeys, function (subKey, indx) { |
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 (_.isObject(data[subKey]) && !_.isNull(data[subKey]) && _.isUndefined(data[subKey].length) && _.keys(data[subKey]).length > 0) { // If we have another nested document |
16 |
| - subKeys[indx] = retrieveSubHeading(newKey, data[subKey]); // Recur on the subdocument to retrieve the full key name |
17 |
| - } else { |
18 |
| - subKeys[indx] = (options.DELIMITER.WRAP || '') + (newKey || '') + (options.DELIMITER.WRAP || ''); // Set the key name since we don't have a sub document |
19 |
| - } |
20 |
| - }); |
21 |
| - return subKeys.join(options.DELIMITER.FIELD); // Return the headings joined by our field delimiter |
22 |
| -}; |
| 6 | +var options = {}; // Initialize the options - this will be populated when the json2csv function is called. |
23 | 7 |
|
24 | 8 | // Retrieve the headings for all documents and return it. This checks that all documents have the same schema.
|
25 |
| -var retrieveHeading = function (data, cb) { |
26 |
| - var keys = _.keys(data); // Retrieve the current data keys |
27 |
| - _.each(keys, function (key, indx) { // for each key |
| 9 | +var generateHeading = function(data, cb) { |
| 10 | + var keys = _.map(_.keys(data), function (key, indx) { // for each key |
28 | 11 | if (_.isObject(data[key])) {
|
29 | 12 | // if the data at the key is a document, then we retrieve the subHeading starting with an empty string heading and the doc
|
30 |
| - keys[indx] = retrieveSubHeading('', data[key]); |
| 13 | + return generateSubHeading('', data[key]); |
31 | 14 | }
|
| 15 | + return key; |
| 16 | + }); |
| 17 | + |
| 18 | + // TODO: check for consistent schema |
| 19 | + |
| 20 | + keys = _.map(keys, function(keyList) { |
| 21 | + return _.flatten(keyList).join(options.DELIMITER.FIELD); |
32 | 22 | });
|
| 23 | + |
33 | 24 | // Retrieve the unique array of headings (keys)
|
34 | 25 | keys = _.uniq(keys);
|
| 26 | + |
35 | 27 | // If we have more than 1 unique list, then not all docs have the same schema - report an error
|
36 | 28 | if (keys.length > 1) { throw new Error('Not all documents have the same schema.', keys); }
|
37 |
| - return cb(null, _.flatten(keys).join(options.DELIMITER.FIELD)); // Return headings back |
| 29 | + |
| 30 | + return cb(null, keys); |
| 31 | +}; |
| 32 | + |
| 33 | +// Takes the parent heading and this doc's data and creates the subdocument headings (string) |
| 34 | +var generateSubHeading = function(heading, data) { |
| 35 | + var subKeys, // retrieve the keys from the current document |
| 36 | + newKey = ''; // temporary variable to aid in determining the heading - used to generate the 'nested' headings |
| 37 | + |
| 38 | + subKeys = _.map(_.keys(data), function (subKey) { |
| 39 | + // 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 |
| 40 | + newKey = heading === '' ? subKey : heading + '.' + subKey; |
| 41 | + if (_.isObject(data[subKey]) && !_.isNull(data[subKey]) && _.isUndefined(data[subKey].length) && _.keys(data[subKey]).length > 0) { // If we have another nested document |
| 42 | + return generateSubHeading(newKey, data[subKey]); // Recur on the sub-document to retrieve the full key name |
| 43 | + } else { |
| 44 | + return options.DELIMITER.WRAP + newKey + options.DELIMITER.WRAP; // Set the key name since we don't have a sub document |
| 45 | + } |
| 46 | + }); |
| 47 | + |
| 48 | + return subKeys; // Return the headings joined by our field delimiter |
38 | 49 | };
|
39 | 50 |
|
40 | 51 | // Convert the given data with the given keys
|
@@ -71,17 +82,17 @@ module.exports = {
|
71 | 82 | // Takes options as a document, data as a JSON document array, and a callback that will be used to report the results
|
72 | 83 | json2csv: function (opts, data, callback) {
|
73 | 84 | if (!callback) { throw new Error('A callback is required!'); } // If a callback wasn't provided, throw an error
|
74 |
| - if (!opts) { callback(new Error('Options were not passed and are required.')); return null; } // Shouldn't happen, but just in case |
| 85 | + if (!opts) { return callback(new Error('Options were not passed and are required.')); } // Shouldn't happen, but just in case |
75 | 86 | else { options = opts; } // Options were passed, set the global options value
|
76 |
| - if (!data) { callback(new Error('Cannot call json2csv on ' + data + '.')); return null; } // If we don't receive data, report an error |
| 87 | + if (!data) { return callback(new Error('Cannot call json2csv on ' + data + '.')); } // If we don't receive data, report an error |
77 | 88 | if (!_.isObject(data)) { // If the data was not a single document or an array of documents
|
78 |
| - return cb(new Error('Data provided was not an array of documents.')); // Report the error back to the caller |
| 89 | + return callback(new Error('Data provided was not an array of documents.')); // Report the error back to the caller |
79 | 90 | } else if (_.isObject(data) && !data.length) { // Single document, not an array
|
80 | 91 | data = [data]; // Convert to an array of the given document
|
81 | 92 | }
|
82 | 93 |
|
83 | 94 | // Retrieve the heading and the CSV asynchronously in parallel
|
84 |
| - async.parallel([_.partial(retrieveHeading, data), _.partial(generateCsv, data)], function (err, res) { |
| 95 | + async.parallel([_.partial(generateHeading, data), _.partial(generateCsv, data)], function (err, res) { |
85 | 96 | if (!err) {
|
86 | 97 | // Data received with no errors, join the two responses with an end of line delimiter to setup heading and CSV body
|
87 | 98 | return callback(null, res.join(options.EOL));
|
|
0 commit comments