UNPKG

json-2-csv

Version:

A JSON to CSV and CSV to JSON converter that natively supports sub-documents and auto-generates the CSV heading.

547 lines (545 loc) 18.5 kB
<!doctype html> <html lang="en"> <head> <title>Code coverage report for lib/json-2-csv.js</title> <meta charset="utf-8"> <link rel="stylesheet" href="../prettify.css"> <link rel="stylesheet" href="../base.css"> <style type='text/css'> div.coverage-summary .sorter { background-image: url(../sort-arrow-sprite.png); } </style> </head> <body> <div class="header high"> <h1>Code coverage report for <span class="entity">lib/json-2-csv.js</span></h1> <h2> Statements: <span class="metric">95% <small>(57 / 60)</small></span> &nbsp;&nbsp;&nbsp;&nbsp; Branches: <span class="metric">90% <small>(45 / 50)</small></span> &nbsp;&nbsp;&nbsp;&nbsp; Functions: <span class="metric">100% <small>(14 / 14)</small></span> &nbsp;&nbsp;&nbsp;&nbsp; Lines: <span class="metric">96.36% <small>(53 / 55)</small></span> &nbsp;&nbsp;&nbsp;&nbsp; Ignored: <span class="metric"><span class="ignore-none">none</span></span> &nbsp;&nbsp;&nbsp;&nbsp; </h2> <div class="path"><a href="../index.html">All files</a> &#187; <a href="index.html">lib/</a> &#187; json-2-csv.js</div> </div> <div class="body"> <pre><table class="coverage"> <tr><td class="line-count">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168</td><td class="line-coverage"><span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-yes">106</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">98</span> <span class="cline-any cline-yes">276</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">276</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">98</span> <span class="cline-any cline-yes">22</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">76</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">76</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">254</span> <span class="cline-any cline-yes">254</span> <span class="cline-any cline-yes">30</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">76</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">66</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-yes">366</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">366</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">910</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">910</span> <span class="cline-any cline-yes">90</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">820</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">366</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">260</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">738</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-yes">738</span> <span class="cline-any cline-yes">30</span> <span class="cline-any cline-yes">708</span> <span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">708</span> <span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">708</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">96</span> <span class="cline-any cline-yes">260</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">1</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">149</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">128</span> <span class="cline-any cline-yes">128</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">128</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">108</span> <span class="cline-any cline-yes">2</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">106</span> <span class="cline-any cline-yes">22</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">106</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">96</span> <span class="cline-any cline-yes">20</span> <span class="cline-any cline-yes">56</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">96</span> <span class="cline-any cline-yes">90</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">96</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-yes">10</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">'use strict'; &nbsp; var _ = require('underscore'), constants = require('./constants'), path = require('doc-path'), Promise = require('bluebird'); &nbsp; var options = {}; // Initialize the options - this will be populated when the json2csv function is called. &nbsp; /** * Retrieve the headings for all documents and return it. * This checks that all documents have the same schema. * @param data * @returns {Promise} */ var generateHeading = function(data) { if (options.KEYS) { return Promise.resolve(options.KEYS); } &nbsp; var keys = _.map(data, function (document, indx) { // for each key <span class="missing-if-branch" title="else path not taken" >E</span>if (_.isObject(document)) { // if the data at the key is a document, then we retrieve the subHeading starting with an empty string heading and the doc return generateDocumentHeading('', document); } }); &nbsp; // Check for a consistent schema that does not require the same order: // if we only have one document - then there is no possibility of multiple schemas if (keys &amp;&amp; keys.length &lt;= 1) { return Promise.resolve(_.flatten(keys) || <span class="branch-1 cbranch-no" title="branch not covered" >[])</span>; } // else - multiple documents - ensure only one schema (regardless of field ordering) var firstDocSchema = _.flatten(keys[0]), schemaDifferences = 0; &nbsp; _.each(keys, function (keyList) { // If there is a difference between the schemas, increment the counter of schema inconsistencies var diff = _.difference(firstDocSchema, _.flatten(keyList)); if (!_.isEqual(diff, [])) { schemaDifferences++; } }); &nbsp; // If there are schema inconsistencies, throw a schema not the same error if (schemaDifferences) { return Promise.reject(new Error(constants.Errors.json2csv.notSameSchema)); } &nbsp; return Promise.resolve(_.flatten(keys[0])); }; &nbsp; /** * Takes the parent heading and this doc's data and creates the subdocument headings (string) * @param heading * @param data * @returns {Array} */ var generateDocumentHeading = function(heading, data) { var keyName = ''; // temporary variable to aid in determining the heading - used to generate the 'nested' headings &nbsp; var documentKeys = _.map(_.keys(data), function (currentKey) { // 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 keyName = heading ? heading + '.' + currentKey : currentKey; &nbsp; // If we have another nested document, recur on the sub-document to retrieve the full key name if (_.isObject(data[currentKey]) &amp;&amp; !_.isNull(data[currentKey]) &amp;&amp; _.isUndefined(data[currentKey].length) &amp;&amp; _.keys(data[currentKey]).length) { return generateDocumentHeading(keyName, data[currentKey]); } // Otherwise return this key name since we don't have a sub document return keyName; }); &nbsp; return documentKeys; // Return the headings joined by our field delimiter }; &nbsp; /** * Convert the given data with the given keys * @param data * @param keys * @returns {Array} */ var convertData = function (data, keys) { // Reduce each key in the data to its CSV value return _.reduce(keys, function (output, key) { // Add the CSV representation of the data at the key in the document to the output array return output.concat(convertField(path.evaluatePath(data, key))); }, []); }; &nbsp; /** * Convert the given value to the CSV representation of the value * @param value * @param output */ var convertField = function (value) { if (_.isArray(value)) { // We have an array of values return options.DELIMITER.WRAP + '[' + value.join(options.DELIMITER.ARRAY) + ']' + options.DELIMITER.WRAP; } else <span class="missing-if-branch" title="if path not taken" >I</span>if (_.isDate(value)) { // If we have a date <span class="cstat-no" title="statement not covered" > return options.DELIMITER.WRAP + value.toString() + options.DELIMITER.WRAP;</span> } else <span class="missing-if-branch" title="if path not taken" >I</span>if (_.isObject(value)) { // If we have an object <span class="cstat-no" title="statement not covered" > return options.DELIMITER.WRAP + convertData(value, _.keys(value)) + options.DELIMITER.WRAP; </span>// Push the recursively generated CSV } return options.DELIMITER.WRAP + (value ? value.toString() : '') + options.DELIMITER.WRAP; // Otherwise push the current value }; &nbsp; /** * Generate the CSV representing the given data. * @param data * @param headingKeys * @returns {*} */ var generateCsv = function (data, headingKeys) { // Reduce each JSON document in data to a CSV string and append it to the CSV accumulator return [headingKeys].concat(_.reduce(data, function (csv, doc) { return csv += convertData(doc, headingKeys).join(options.DELIMITER.FIELD) + options.EOL; }, '')); }; &nbsp; module.exports = { &nbsp; /** * Internally exported json2csv function * Takes options as a document, data as a JSON document array, and a callback that will be used to report the results * @param opts Object options object * @param data String csv string * @param callback Function callback function */ json2csv: function (opts, data, callback) { // If a callback wasn't provided, throw an error if (!callback) { throw new Error(constants.Errors.callbackRequired); } &nbsp; // Shouldn't happen, but just in case <span class="missing-if-branch" title="if path not taken" >I</span>if (!opts) { <span class="cstat-no" title="statement not covered" >return callback(new Error(constants.Errors.optionsRequired)); </span>} options = opts; // Options were passed, set the global options value &nbsp; // If we don't receive data, report an error if (!data) { return callback(new Error(constants.Errors.json2csv.cannotCallJson2CsvOn + data + '.')); } &nbsp; // If the data was not a single document or an array of documents if (!_.isObject(data)) { return callback(new Error(constants.Errors.json2csv.dataNotArrayOfDocuments)); // Report the error back to the caller } // Single document, not an array else if (_.isObject(data) &amp;&amp; !data.length) { data = [data]; // Convert to an array of the given document } &nbsp; // Retrieve the heading and then generate the CSV with the keys that are identified generateHeading(data) .then(_.partial(generateCsv, data)) .spread(function (csvHeading, csvData) { // If the fields are supposed to be wrapped... (only perform this if we are actually prepending the header) if (options.DELIMITER.WRAP &amp;&amp; options.PREPEND_HEADER) { csvHeading = _.map(csvHeading, function(headingKey) { return options.DELIMITER.WRAP + headingKey + options.DELIMITER.WRAP; }); } // If we are prepending the header, then join the csvHeading fields if (options.PREPEND_HEADER) { csvHeading = csvHeading.join(options.DELIMITER.FIELD); } &nbsp; // If we are prepending the header, then join the header and data by EOL, otherwise just return the data return callback(null, options.PREPEND_HEADER ? csvHeading + options.EOL + csvData : csvData); }) .catch(function (err) { return callback(err); }); } &nbsp; };</pre></td></tr> </table></pre> </div> <div class="footer"> <div class="meta">Generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Sun Apr 26 2015 12:50:58 GMT-0400 (EDT)</div> </div> <script src="../prettify.js"></script> <script> window.onload = function () { if (typeof prettyPrint === 'function') { prettyPrint(); } }; </script> <script src="../sorter.js"></script> </body> </html>