Skip to content

Commit dd2a87a

Browse files
committed
Merge pull request #21 from mrodrig/refactor-code
Cleaning up code and comments
2 parents d21848b + caa2697 commit dd2a87a

File tree

7 files changed

+298
-160
lines changed

7 files changed

+298
-160
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ $ npm run coverage
144144

145145
Current Coverage is:
146146
```
147-
Statements : 93.63% ( 147/157 )
148-
Branches : 87.91% ( 80/91 )
149-
Functions : 100% ( 36/36 )
150-
Lines : 95.86% ( 139/145 )
147+
Statements : 94.49% ( 120/127 )
148+
Branches : 90.24% ( 74/82 )
149+
Functions : 100% ( 30/30 )
150+
Lines : 97.39% ( 112/115 )
151151
```
152152

153153
## Features

bower.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{
22
"name": "json-2-csv",
3-
"version": "1.1.3",
3+
"version": "1.2.0",
44
"homepage": "https://github.com/mrodrig/json-2-csv",
55
"moduleType": [
66
"node"
77
],
8+
"main": "lib/converter.js",
89
"license": "MIT",
910
"ignore": [
1011
"**/.*",
@@ -14,7 +15,7 @@
1415
"tests"
1516
],
1617
"dependencies": {
17-
"underscore": "1.6.0",
18+
"underscore": "1.8.3",
1819
"bluebird": "2.9.24"
1920
}
2021
}

lib/converter.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
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
constants = require('./constants'), // Require in constants
6+
Promise = require('bluebird'),
67
_ = require('underscore'); // Require underscore
78

89
/**
@@ -17,8 +18,10 @@ var defaultOptions = constants.DefaultOptions;
1718
*/
1819
var buildOptions = function (opts, cb) {
1920
opts = _.defaults(opts || {}, defaultOptions);
21+
2022
// Note: _.defaults does a shallow default, we need to deep copy the DELIMITER object
2123
opts.DELIMITER = _.defaults(opts.DELIMITER || {}, defaultOptions.DELIMITER);
24+
2225
// If the delimiter fields are the same, report an error to the caller
2326
if (opts.DELIMITER.FIELD === opts.DELIMITER.ARRAY) { return cb(new Error(constants.Errors.delimitersMustDiffer)); }
2427
// Otherwise, send the options back
@@ -28,17 +31,22 @@ var buildOptions = function (opts, cb) {
2831
// Export the following functions that will be client accessible
2932
module.exports = {
3033

31-
// Client accessible json2csv function
32-
// Takes an array of JSON documents to be converted,
33-
// a callback that will be called with (err, csv) after
34-
// processing is completed, and optional options
34+
/**
35+
* Client accessible json2csv function
36+
* Takes an array of JSON documents to be converted, a callback that will be called with (err, csv)
37+
* after processing is complete, and optional options
38+
* @param array Object[] data to be converted
39+
* @param callback Function callback
40+
* @param opts Object options object
41+
*/
3542
json2csv: function (array, callback, opts) {
3643
// If this was promisified (callback and opts are swapped) then fix the argument order.
3744
if (_.isObject(callback) && !_.isFunction(callback)) {
3845
var func = opts;
3946
opts = callback;
4047
callback = func;
4148
}
49+
4250
buildOptions(opts, function (err, options) { // Build the options
4351
if (err) {
4452
return callback(err);
@@ -49,17 +57,22 @@ module.exports = {
4957
},
5058

5159

52-
// Client accessible csv2json function
53-
// Takes a string of CSV to be converted to a JSON document array,
54-
// a callback that will be called with (err, csv) after
55-
// processing is completed, and optional options
60+
/**
61+
* Client accessible csv2json function
62+
* Takes a string of CSV to be converted to a JSON document array, a callback that will be called
63+
* with (err, json) after processing is complete, and optional options
64+
* @param csv
65+
* @param callback
66+
* @param opts
67+
*/
5668
csv2json: function (csv, callback, opts) {
5769
// If this was promisified (callback and opts are swapped) then fix the argument order.
5870
if (_.isObject(callback) && !_.isFunction(callback)) {
5971
var func = opts;
6072
opts = callback;
6173
callback = func;
6274
}
75+
6376
buildOptions(opts, function (err, options) { // Build the options
6477
if (err) {
6578
return callback(err);

lib/csv-2-json.js

Lines changed: 97 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,136 @@
11
'use strict';
22

33
var _ = require('underscore'),
4+
path = require('doc-path'),
45
constants = require('./constants');
56

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

8-
// Generate the JSON heading from the CSV
9+
/**
10+
* Generate the JSON heading from the CSV
11+
* @param lines
12+
* @param callback
13+
* @returns {*}
14+
*/
915
var retrieveHeading = function (lines, callback) {
10-
if (!lines.length) { // If there are no lines passed in, then throw an error
16+
// If there are no lines passed in, return an error
17+
if (!lines.length) {
1118
return callback(new Error(constants.Errors.csv2json.noDataRetrieveHeading)); // Pass an error back to the user
1219
}
13-
var heading = lines[0].split(options.DELIMITER.FIELD); // Grab the top line (header line) and split by the field delimiter
14-
heading = _.map(heading, function (headerKey, index) {
15-
return {
16-
value: headerKey,
17-
index: index
18-
}
19-
});
20-
return heading;
21-
};
2220

23-
// Add a nested key and its value in the given document
24-
var addNestedKey = function (key, value, doc) {
25-
var subDocumentRoot = doc, // This is the document that we will be using to add the nested keys to.
26-
trackerDocument = subDocumentRoot, // This is the document that will use to iterate through the subDocument, starting at the root
27-
nestedKeys = key.split('.'), // Array of all keys and sub keys for the document
28-
finalKey = nestedKeys.pop(); // Retrieve the last sub key.
29-
_.each(nestedKeys, function (nestedKey) {
30-
if (keyExists(nestedKey, trackerDocument)) { // This nestedKey already exists, use an existing doc
31-
trackerDocument = trackerDocument[nestedKey]; // Update the trackerDocument to use the existing document
32-
} else {
33-
trackerDocument[nestedKey] = {}; // Add document at the current subKey
34-
trackerDocument = trackerDocument[nestedKey]; // Update trackerDocument to be the added doc for the subKey
35-
}
36-
});
37-
trackerDocument[finalKey] = value; // Set the final layer key to the value
38-
return subDocumentRoot; // Return the document with the nested document structure setup
39-
};
40-
41-
// Helper function to check if the given key already exists in the given document
42-
var keyExists = function (key, doc) {
43-
return (!_.isUndefined(doc[key])); // If the key doesn't exist, then the type is 'undefined'
21+
// Generate and return the heading keys
22+
return _.map(lines[0].split(options.DELIMITER.FIELD),
23+
function (headerKey, index) {
24+
return {
25+
value: headerKey,
26+
index: index
27+
};
28+
});
4429
};
4530

31+
/**
32+
* Does the given value represent an array?
33+
* @param value
34+
* @returns {boolean}
35+
*/
4636
var isArrayRepresentation = function (value) {
47-
return (value && value.indexOf('[') === 0 && value.lastIndexOf(']') === value.length-1);
37+
// Verify that there is a value and it starts with '[' and ends with ']'
38+
return (value && /^\[.*\]$/.test(value));
4839
};
4940

50-
var convertArrayRepresentation = function (val) {
51-
val = _.filter(val.substring(1, val.length-1).split(options.DELIMITER.ARRAY), function (value) {
41+
/**
42+
* Converts the value from a CSV 'array'
43+
* @param val
44+
* @returns {Array}
45+
*/
46+
var convertArrayRepresentation = function (arrayRepresentation) {
47+
// Remove the '[' and ']' characters
48+
arrayRepresentation = arrayRepresentation.replace(/(\[|\])/g, '');
49+
50+
// Split the arrayRepresentation into an array by the array delimiter
51+
arrayRepresentation = arrayRepresentation.split(options.DELIMITER.ARRAY);
52+
53+
// Filter out non-empty strings
54+
return _.filter(arrayRepresentation, function (value) {
5255
return value;
5356
});
54-
_.each(val, function (value, indx) {
55-
if (isArrayRepresentation(value)) {
56-
val[indx] = convertArrayRepresentation(value);
57-
}
58-
});
59-
return val;
6057
};
6158

62-
// Create a JSON document with the given keys (designated by the CSV header) and the values (from the given line)
63-
var createDoc = function (keys, line) {
64-
if (line == '') { return false; } // If we have an empty line, then return false so we can remove all blank lines (falsy values)
65-
var doc = {}, // JSON document to start with and manipulate
66-
val, // Temporary variable to set the current key's value to
67-
line = line.trim().split(options.DELIMITER.FIELD); // Split the line using the given field delimiter after trimming whitespace
68-
_.each(keys, function (key, indx) {
69-
val = line[key.index] === '' ? null : line[key.index];
59+
/**
60+
* Create a JSON document with the given keys (designated by the CSV header)
61+
* and the values (from the given line)
62+
* @param keys String[]
63+
* @param line String
64+
* @returns {Object} created json document
65+
*/
66+
var createDocument = function (keys, line) {
67+
var line = line.split(options.DELIMITER.FIELD), // Split the line using the given field delimiter after trimming whitespace
68+
val; // Temporary variable to set the current key's value to
69+
70+
// Reduce the keys into a JSON document representing the given line
71+
return _.reduce(keys, function (document, key) {
72+
// If there is a value at the key's index in the line, set the value; otherwise null
73+
val = line[key.index] ? line[key.index] : null;
74+
75+
// If the value is an array representation, convert it
7076
if (isArrayRepresentation(val)) {
7177
val = convertArrayRepresentation(val);
7278
}
73-
if (key.value.indexOf('.')) { // If key has '.' representing nested document
74-
doc = addNestedKey(key.value, val, doc); // Update the document to add the nested key structure
75-
} else { // Else we just have a straight key:value mapping
76-
doc[key] = val; // Set the value at the current key
77-
}
78-
});
79-
return doc; // Return the created document
79+
// Otherwise add the key and value to the document
80+
return path.setPath(document, key.value, val);
81+
}, {});
8082
};
8183

82-
// Main wrapper function to convert the CSV to the JSON document array
84+
/**
85+
* Main helper function to convert the CSV to the JSON document array
86+
* @param lines String[]
87+
* @param callback Function callback function
88+
* @returns {Array}
89+
*/
8390
var convertCSV = function (lines, callback) {
8491
var generatedHeaders = retrieveHeading(lines, callback), // Retrieve the headings from the CSV, unless the user specified the keys
85-
jsonDocs = [], // Create an array that we can add the generated documents to
92+
nonHeaderLines = lines.splice(1), // All lines except for the header line
93+
// If the user provided keys, filter the generated keys to just the user provided keys so we also have the key index
8694
headers = options.KEYS ? _.filter(generatedHeaders, function (headerKey) {
8795
return _.contains(options.KEYS, headerKey.value);
8896
}) : generatedHeaders;
89-
lines = lines.splice(1); // Grab all lines except for the header
90-
_.each(lines, function (line) { // For each line, create the document and add it to the array of documents
91-
jsonDocs.push(createDoc(headers, line));
92-
});
93-
return _.filter(jsonDocs, function (doc) { return doc !== false; }); // Return all non 'falsey' values to filter blank lines
97+
98+
return _.reduce(nonHeaderLines, function (documentArray, line) { // For each line, create the document and add it to the array of documents
99+
if (!line) { return documentArray; } // skip over empty lines
100+
var generatedDocument = createDocument(headers, line.trim());
101+
return documentArray.concat(generatedDocument);
102+
}, []);
94103
};
95104

96105
module.exports = {
97-
98-
// Function to export internally
99-
// Takes options as a document, data as a CSV string, and a callback that will be used to report the results
106+
107+
/**
108+
* Internally exported csv2json function
109+
* Takes options as a document, data as a CSV string, and a callback that will be used to report the results
110+
* @param opts Object options object
111+
* @param data String csv string
112+
* @param callback Function callback function
113+
*/
100114
csv2json: function (opts, data, callback) {
101-
if (!callback) { throw new Error(constants.Errors.callbackRequired); } // If a callback wasn't provided, throw an error
102-
if (!opts) { return callback(new Error(constants.Errors.optionsRequired)); return null; } // Shouldn't happen, but just in case
103-
else { options = opts; } // Options were passed, set the global options value
104-
if (!data) { return callback(new Error(constants.Errors.csv2json.cannotCallCsv2JsonOn + data + '.')); return null; } // If we don't receive data, report an error
105-
if (!_.isString(data)) { // The data is not a string
115+
// If a callback wasn't provided, throw an error
116+
if (!callback) { throw new Error(constants.Errors.callbackRequired); }
117+
118+
// Shouldn't happen, but just in case
119+
if (!opts) { return callback(new Error(constants.Errors.optionsRequired)); return null; }
120+
options = opts; // Options were passed, set the global options value
121+
122+
// If we don't receive data, report an error
123+
if (!data) { return callback(new Error(constants.Errors.csv2json.cannotCallCsv2JsonOn + data + '.')); return null; }
124+
125+
// The data provided is not a string
126+
if (!_.isString(data)) {
106127
return callback(new Error(constants.Errors.csv2json.csvNotString)); // Report an error back to the caller
107128
}
108-
var lines = data.split(options.EOL); // Split the CSV into lines using the specified EOL option
109-
var json = convertCSV(lines, callback); // Retrieve the JSON document array
110-
callback(null, json); // Send the data back to the caller
129+
130+
// Split the CSV into lines using the specified EOL option
131+
var lines = data.split(options.EOL),
132+
json = convertCSV(lines, callback); // Retrieve the JSON document array
133+
return callback(null, json); // Send the data back to the caller
111134
}
112135

113136
};

0 commit comments

Comments
 (0)