1
1
'use strict' ;
2
2
3
3
var _ = require ( 'underscore' ) ,
4
+ path = require ( 'doc-path' ) ,
4
5
constants = require ( './constants' ) ;
5
6
6
7
var options = { } ; // Initialize the options - this will be populated when the csv2json function is called.
7
8
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
+ */
9
15
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 ) {
11
18
return callback ( new Error ( constants . Errors . csv2json . noDataRetrieveHeading ) ) ; // Pass an error back to the user
12
19
}
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
- } ;
22
20
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
+ } ) ;
44
29
} ;
45
30
31
+ /**
32
+ * Does the given value represent an array?
33
+ * @param value
34
+ * @returns {boolean }
35
+ */
46
36
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 ) ) ;
48
39
} ;
49
40
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 ) {
52
55
return value ;
53
56
} ) ;
54
- _ . each ( val , function ( value , indx ) {
55
- if ( isArrayRepresentation ( value ) ) {
56
- val [ indx ] = convertArrayRepresentation ( value ) ;
57
- }
58
- } ) ;
59
- return val ;
60
57
} ;
61
58
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
70
76
if ( isArrayRepresentation ( val ) ) {
71
77
val = convertArrayRepresentation ( val ) ;
72
78
}
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
+ } , { } ) ;
80
82
} ;
81
83
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
+ */
83
90
var convertCSV = function ( lines , callback ) {
84
91
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
86
94
headers = options . KEYS ? _ . filter ( generatedHeaders , function ( headerKey ) {
87
95
return _ . contains ( options . KEYS , headerKey . value ) ;
88
96
} ) : 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
+ } , [ ] ) ;
94
103
} ;
95
104
96
105
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
+ */
100
114
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 ) ) {
106
127
return callback ( new Error ( constants . Errors . csv2json . csvNotString ) ) ; // Report an error back to the caller
107
128
}
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
111
134
}
112
135
113
136
} ;
0 commit comments