Skip to content

Commit e7f6f0a

Browse files
committed
Refactored how headers are generated to allow for easier schema consistency checking, will continue on this tomorrow
1 parent e8e826a commit e7f6f0a

File tree

2 files changed

+50
-34
lines changed

2 files changed

+50
-34
lines changed

lib/converter.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ var json2Csv = require('./json-2-csv'), // Require our json-2-csv code
44
csv2Json = require('./csv-2-json'), // Require our csv-2-json code
55
_ = require('underscore'); // Require underscore
66

7-
// Default options; By using a function this is essentially a 'static' variable
7+
/**
8+
* Default options
9+
*/
810
var defaultOptions = {
911
DELIMITER : {
1012
FIELD : ',',
@@ -15,16 +17,19 @@ var defaultOptions = {
1517
PARSE_CSV_NUMBERS : false
1618
};
1719

18-
// Build the options to be passed to the appropriate function
19-
// If a user does not provide custom options, then we use our default
20-
// If options are provided, then we set each valid key that was passed
20+
/**
21+
* Build the options to be passed to the appropriate function
22+
* If a user does not provide custom options, then we use our default
23+
* If options are provided, then we set each valid key that was passed
24+
*/
2125
var buildOptions = function (opts, cb) {
22-
opts = opts ? opts : {}; // If undefined, set to an empty doc
23-
var out = _.defaults(opts, defaultOptions);
26+
opts = _.defaults(opts || {}, defaultOptions);
27+
// Note: _.defaults does a shallow default, we need to deep copy the DELIMITER object
28+
opts.DELIMITER = _.defaults(opts.DELIMITER || {}, defaultOptions.DELIMITER);
2429
// If the delimiter fields are the same, report an error to the caller
25-
if (out.DELIMITER.FIELD === out.DELIMITER.ARRAY) { return cb(new Error('The field and array delimiters must differ.')); }
30+
if (opts.DELIMITER.FIELD === opts.DELIMITER.ARRAY) { return cb(new Error('The field and array delimiters must differ.')); }
2631
// Otherwise, send the options back
27-
else { return cb(null, out); }
32+
else { return cb(null, opts); }
2833
};
2934

3035
// Export the following functions that will be client accessible

lib/json-2-csv.js

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,49 @@
33
var _ = require('underscore'),
44
async = require('async');
55

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.
237

248
// 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
2811
if (_.isObject(data[key])) {
2912
// 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]);
3114
}
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);
3222
});
23+
3324
// Retrieve the unique array of headings (keys)
3425
keys = _.uniq(keys);
26+
3527
// If we have more than 1 unique list, then not all docs have the same schema - report an error
3628
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
3849
};
3950

4051
// Convert the given data with the given keys
@@ -71,17 +82,17 @@ module.exports = {
7182
// Takes options as a document, data as a JSON document array, and a callback that will be used to report the results
7283
json2csv: function (opts, data, callback) {
7384
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
7586
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
7788
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
7990
} else if (_.isObject(data) && !data.length) { // Single document, not an array
8091
data = [data]; // Convert to an array of the given document
8192
}
8293

8394
// 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) {
8596
if (!err) {
8697
// Data received with no errors, join the two responses with an end of line delimiter to setup heading and CSV body
8798
return callback(null, res.join(options.EOL));

0 commit comments

Comments
 (0)