Skip to content

Commit b588a06

Browse files
committed
more work
1 parent 8347b1c commit b588a06

File tree

4 files changed

+201
-60
lines changed

4 files changed

+201
-60
lines changed

lib/csv-2-json.js

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

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

67
var options = {}; // Initialize the options - this will be populated when the csv2json function is called.
@@ -12,42 +13,19 @@ var options = {}; // Initialize the options - this will be populated when the cs
1213
* @returns {*}
1314
*/
1415
var retrieveHeading = function (lines, callback) {
15-
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) {
1618
return callback(new Error(constants.Errors.csv2json.noDataRetrieveHeading)); // Pass an error back to the user
1719
}
1820

19-
var heading = _.map(lines[0].split(options.DELIMITER.FIELD),
21+
// Generate and return the heading keys
22+
return _.map(lines[0].split(options.DELIMITER.FIELD),
2023
function (headerKey, index) {
2124
return {
2225
value: headerKey,
2326
index: index
2427
};
2528
});
26-
return heading;
27-
};
28-
29-
/**
30-
* Add a nested key and its value in the given document
31-
* @param key String
32-
* @param value String
33-
* @param doc Object
34-
* @returns {*}
35-
*/
36-
var addNestedKey = function (key, value, doc) {
37-
var subDocumentRoot = doc, // This is the document that we will be using to add the nested keys to.
38-
trackerDocument = subDocumentRoot, // This is the document that will use to iterate through the subDocument, starting at the root
39-
nestedKeys = key.split('.'), // Array of all keys and sub keys for the document
40-
finalKey = nestedKeys.pop(); // Retrieve the last sub key.
41-
_.each(nestedKeys, function (nestedKey) {
42-
if (trackerDocument[nestedKey]) { // This nestedKey already exists, use an existing doc
43-
trackerDocument = trackerDocument[nestedKey]; // Update the trackerDocument to use the existing document
44-
} else {
45-
trackerDocument[nestedKey] = {}; // Add document at the current subKey
46-
trackerDocument = trackerDocument[nestedKey]; // Update trackerDocument to be the added doc for the subKey
47-
}
48-
});
49-
trackerDocument[finalKey] = value; // Set the final layer key to the value
50-
return subDocumentRoot; // Return the document with the nested document structure setup
5129
};
5230

5331
/**
@@ -56,24 +34,26 @@ var addNestedKey = function (key, value, doc) {
5634
* @returns {boolean}
5735
*/
5836
var isArrayRepresentation = function (value) {
59-
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));
6039
};
6140

6241
/**
6342
* Converts the value from a CSV 'array'
6443
* @param val
6544
* @returns {Array}
6645
*/
67-
var convertArrayRepresentation = function (val) {
68-
val = _.filter(val.substring(1, val.length-1).split(options.DELIMITER.ARRAY), function (value) {
69-
return value;
70-
});
71-
_.each(val, function (value, indx) {
72-
if (isArrayRepresentation(value)) {
73-
val[indx] = convertArrayRepresentation(value);
74-
}
75-
});
76-
return val;
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) {
55+
return value;
56+
});
7757
};
7858

7959
/**
@@ -83,23 +63,22 @@ var convertArrayRepresentation = function (val) {
8363
* @param line String
8464
* @returns {Object} created json document
8565
*/
86-
var createDoc = function (keys, line) {
87-
if (line == '') { return false; } // If we have an empty line, then return false so we can remove all blank lines (falsy values)
88-
var doc = {}, // JSON document to start with and manipulate
89-
val, // Temporary variable to set the current key's value to
90-
line = line.trim().split(options.DELIMITER.FIELD); // Split the line using the given field delimiter after trimming whitespace
91-
_.each(keys, function (key, indx) {
92-
val = line[key.index] === '' ? null : line[key.index];
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 no value (empty string) at the key's index in the line, set the value to null; otherwise the value
73+
val = !line[key.index] ? null : line[key.index];
74+
75+
// If the value is an array representation, convert it
9376
if (isArrayRepresentation(val)) {
9477
val = convertArrayRepresentation(val);
9578
}
96-
if (key.value.indexOf('.')) { // If key has '.' representing nested document
97-
doc = addNestedKey(key.value, val, doc); // Update the document to add the nested key structure
98-
} else { // Else we just have a straight key:value mapping
99-
doc[key] = val; // Set the value at the current key
100-
}
101-
});
102-
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+
}, {});
10382
};
10483

10584
/**
@@ -109,16 +88,20 @@ var createDoc = function (keys, line) {
10988
* @returns {Array}
11089
*/
11190
var convertCSV = function (lines, callback) {
112-
var generatedHeaders = retrieveHeading(lines, callback), // Retrieve the headings from the CSV, unless the user specified the keys
113-
jsonDocs = [], // Create an array that we can add the generated documents to
91+
var nonHeaderLines = lines.splice(1), // All lines that are not the header line
92+
generatedHeaders = retrieveHeading(lines, callback), // Retrieve the headings from the CSV
93+
// If the user specified keys, then filter the generated keys down to the user specified keys so we have the key index
11494
headers = options.KEYS ? _.filter(generatedHeaders, function (headerKey) {
11595
return _.contains(options.KEYS, headerKey.value);
11696
}) : generatedHeaders;
117-
lines = lines.splice(1); // Grab all lines except for the header
118-
_.each(lines, function (line) { // For each line, create the document and add it to the array of documents
119-
jsonDocs.push(createDoc(headers, line));
120-
});
121-
return _.filter(jsonDocs, function (doc) { return doc !== false; }); // Return all non 'falsey' values to filter blank lines
97+
98+
// Reduce the nonHeaderLines into an array of documents representing the lines
99+
return _.reduce(nonHeaderLines, function (jsonDocs, line) {
100+
if (!line) { return; } // skip over empty lines
101+
var generatedDocument = createDocument(headers, line.trim());
102+
jsonDocs.push(generatedDocument);
103+
return jsonDocs;
104+
}, []);
122105
};
123106

124107
module.exports = {

lib/path.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
var _ = require('underscore');
2+
3+
var controller = {};
4+
5+
controller.evaluatePath = function (document, keyPath) {
6+
if (!document) { return null; }
7+
var indexOfDot = _.indexOf(keyPath, '.');
8+
9+
// If there is a '.' in the keyPath, recur on the subdoc and ...
10+
if (indexOfDot >= 0) {
11+
var currentKey = keyPath.slice(0, indexOfDot),
12+
remainingKeyPath = keyPath.slice(indexOfDot + 1);
13+
14+
return controller.evaluatePath(document[currentKey], remainingKeyPath);
15+
}
16+
17+
return document[keyPath];
18+
};
19+
20+
controller.setPath = function (document, keyPath, value) {
21+
if (!document) { throw new Error('error occurred while setting'); }
22+
23+
var indexOfDot = _.indexOf(keyPath, '.');
24+
25+
// If there is a '.' in the keyPath, recur on the subdoc and ...
26+
if (indexOfDot >= 0) {
27+
var currentKey = keyPath.slice(0, indexOfDot),
28+
remainingKeyPath = keyPath.slice(indexOfDot + 1);
29+
30+
if (!document[currentKey]) { document[currentKey] = {}; }
31+
controller.setPath(document[currentKey], remainingKeyPath, value);
32+
} else {
33+
document[keyPath] = value;
34+
}
35+
36+
return document;
37+
};
38+
39+
module.exports = controller;

test/testPath.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
var path = require('../lib/path.js');
2+
3+
var should = require('should'),
4+
assert = require('assert'),
5+
path = require('../lib/path.js'),
6+
_ = require('underscore'),
7+
doc = {};
8+
9+
var pathTests = function () {
10+
describe('evaluatePath', function () {
11+
beforeEach(function () {
12+
doc = {};
13+
});
14+
15+
it('should get a non-nested property that exists', function(done) {
16+
doc.testProperty = 'testValue';
17+
var returnVal = path.evaluatePath(doc, 'testProperty');
18+
returnVal.should.equal('testValue');
19+
done();
20+
});
21+
22+
it('should return null if the non-nested property does not exist', function(done) {
23+
var returnVal = path.evaluatePath(doc, 'testProperty');
24+
assert.equal(returnVal, null);
25+
done();
26+
});
27+
28+
it('should get a non-nested property that exists', function(done) {
29+
doc.testProperty = {testProperty2:'testValue'};
30+
var returnVal = path.evaluatePath(doc, 'testProperty.testProperty2');
31+
returnVal.should.equal('testValue');
32+
done();
33+
});
34+
35+
it('should return null if the nested property does not exist', function(done) {
36+
var returnVal = path.evaluatePath(doc, 'testProperty.testProperty2');
37+
assert.equal(returnVal, null);
38+
done();
39+
});
40+
41+
it('should work with multiple accesses', function (done) {
42+
doc = {
43+
testProperty : {
44+
testProperty2: 'testVal'
45+
},
46+
'testProperty3' : 'testVal2'
47+
};
48+
var returnVal = path.evaluatePath(doc, 'testProperty.testProperty2');
49+
assert.equal(returnVal, 'testVal');
50+
returnVal = path.evaluatePath(doc, 'testProperty3');
51+
assert.equal(returnVal, 'testVal2');
52+
done();
53+
});
54+
});
55+
56+
describe('setPath', function () {
57+
beforeEach(function () {
58+
doc = {};
59+
});
60+
61+
it('should get a non-nested property that exists', function(done) {
62+
var returnVal = path.setPath(doc, 'testProperty', 'null');
63+
assert.equal(returnVal, doc);
64+
done();
65+
});
66+
67+
it('should return null if the non-nested property does not exist', function(done) {
68+
try {
69+
doc = null;
70+
assert.equal(doc, null);
71+
var returnVal = path.setPath(doc, 'testProperty', 'null');
72+
} catch (err) {
73+
err.message.should.equal('error occurred while setting');
74+
done();
75+
}
76+
});
77+
78+
it('should get a non-nested property that exists', function(done) {
79+
var returnVal = path.setPath(doc, 'testProperty.testProperty2', 'testValue');
80+
assert.equal(returnVal, doc);
81+
done();
82+
});
83+
84+
it('should return null if the nested property does not exist', function(done) {
85+
try {
86+
doc = null;
87+
assert.equal(doc, null);
88+
var returnVal = path.setPath(doc, 'testProperty.test', 'null');
89+
} catch (err) {
90+
err.message.should.equal('error occurred while setting');
91+
done();
92+
}
93+
});
94+
95+
it('should work with multiple accesses', function (done) {
96+
var returnVal = path.setPath(doc, 'testProperty.testProperty2', 'testVal');
97+
assert.equal(returnVal, doc);
98+
returnVal = path.setPath(doc, 'testProperty3', 'testVal2');
99+
assert.equal(returnVal, doc);
100+
done();
101+
});
102+
});
103+
};
104+
105+
module.exports = {
106+
runTests: function () {
107+
describe('path.js', function() {
108+
109+
beforeEach(function () {
110+
doc = {};
111+
});
112+
113+
// Run path tests
114+
pathTests();
115+
});
116+
}
117+
};

test/tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
var json2csvTests = require('./testJson2Csv'),
2-
csv2jsonTests = require('./testCsv2Json');
2+
csv2jsonTests = require('./testCsv2Json'),
3+
pathTests = require('./testPath');
34

45
describe('json-2-csv Module', function() {
56
json2csvTests.runTests();
67
csv2jsonTests.runTests();
8+
pathTests.runTests();
79
});

0 commit comments

Comments
 (0)