json2csv.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456
  1. (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  2. 'use strict';
  3. const utils = require('./utils.js');
  4. module.exports = {
  5. deepKeys: deepKeys,
  6. deepKeysFromList: deepKeysFromList
  7. };
  8. /**
  9. * Return the deep keys list for a single document
  10. * @param object
  11. * @param options
  12. * @returns {Array}
  13. */
  14. function deepKeys(object, options) {
  15. options = mergeOptions(options);
  16. if (utils.isObject(object)) {
  17. return generateDeepKeysList('', object, options);
  18. }
  19. return [];
  20. }
  21. /**
  22. * Return the deep keys list for all documents in the provided list
  23. * @param list
  24. * @param options
  25. * @returns Array[Array[String]]
  26. */
  27. function deepKeysFromList(list, options) {
  28. options = mergeOptions(options);
  29. return list.map((document) => { // for each document
  30. if (utils.isObject(document)) {
  31. // if the data at the key is a document, then we retrieve the subHeading starting with an empty string heading and the doc
  32. return deepKeys(document, options);
  33. }
  34. return [];
  35. });
  36. }
  37. function generateDeepKeysList(heading, data, options) {
  38. let keys = Object.keys(data).map((currentKey) => {
  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. let keyName = buildKeyName(heading, currentKey);
  41. // If we have another nested document, recur on the sub-document to retrieve the full key name
  42. if (isDocumentToRecurOn(data[currentKey])) {
  43. return generateDeepKeysList(keyName, data[currentKey], options);
  44. } else if (options.expandArrayObjects && isArrayToRecurOn(data[currentKey])) {
  45. // If we have a nested array that we need to recur on
  46. return processArrayKeys(data[currentKey], keyName, options);
  47. }
  48. // Otherwise return this key name since we don't have a sub document
  49. return keyName;
  50. });
  51. return utils.flatten(keys);
  52. }
  53. /**
  54. * Helper function to handle the processing of arrays when the expandArrayObjects
  55. * option is specified.
  56. * @param subArray
  57. * @param currentKeyPath
  58. * @param options
  59. * @returns {*}
  60. */
  61. function processArrayKeys(subArray, currentKeyPath, options) {
  62. let subArrayKeys = deepKeysFromList(subArray);
  63. if (!subArray.length) {
  64. return options.ignoreEmptyArraysWhenExpanding ? [] : [currentKeyPath];
  65. } else if (subArray.length && utils.flatten(subArrayKeys).length === 0) {
  66. // Has items in the array, but no objects
  67. return [currentKeyPath];
  68. } else {
  69. subArrayKeys = subArrayKeys.map((schemaKeys) => {
  70. if (isEmptyArray(schemaKeys)) {
  71. return [currentKeyPath];
  72. }
  73. return schemaKeys.map((subKey) => buildKeyName(currentKeyPath, subKey));
  74. });
  75. return utils.unique(utils.flatten(subArrayKeys));
  76. }
  77. }
  78. /**
  79. * Function used to generate the key path
  80. * @param upperKeyName String accumulated key path
  81. * @param currentKeyName String current key name
  82. * @returns String
  83. */
  84. function buildKeyName(upperKeyName, currentKeyName) {
  85. if (upperKeyName) {
  86. return upperKeyName + '.' + currentKeyName;
  87. }
  88. return currentKeyName;
  89. }
  90. /**
  91. * Returns whether this value is a document to recur on or not
  92. * @param val Any item whose type will be evaluated
  93. * @returns {boolean}
  94. */
  95. function isDocumentToRecurOn(val) {
  96. return utils.isObject(val) && !utils.isNull(val) && !Array.isArray(val) && Object.keys(val).length;
  97. }
  98. /**
  99. * Returns whether this value is an array to recur on or not
  100. * @param val Any item whose type will be evaluated
  101. * @returns {boolean}
  102. */
  103. function isArrayToRecurOn(val) {
  104. return Array.isArray(val);
  105. }
  106. /**
  107. * Helper function that determines whether or not a value is an empty array
  108. * @param val
  109. * @returns {boolean}
  110. */
  111. function isEmptyArray(val) {
  112. return Array.isArray(val) && !val.length;
  113. }
  114. function mergeOptions(options) {
  115. return {
  116. expandArrayObjects: false,
  117. ignoreEmptyArraysWhenExpanding: false,
  118. ...options || {}
  119. };
  120. }
  121. },{"./utils.js":2}],2:[function(require,module,exports){
  122. 'use strict';
  123. module.exports = {
  124. // underscore replacements:
  125. isString,
  126. isNull,
  127. isError,
  128. isDate,
  129. isFunction,
  130. isUndefined,
  131. isObject,
  132. unique,
  133. flatten
  134. };
  135. /*
  136. * Helper functions which were created to remove underscorejs from this package.
  137. */
  138. function isString(value) {
  139. return typeof value === 'string';
  140. }
  141. function isObject(value) {
  142. return typeof value === 'object';
  143. }
  144. function isFunction(value) {
  145. return typeof value === 'function';
  146. }
  147. function isNull(value) {
  148. return value === null;
  149. }
  150. function isDate(value) {
  151. return value instanceof Date;
  152. }
  153. function isUndefined(value) {
  154. return typeof value === 'undefined';
  155. }
  156. function isError(value) {
  157. return Object.prototype.toString.call(value) === '[object Error]';
  158. }
  159. function unique(array) {
  160. return [...new Set(array)];
  161. }
  162. function flatten(array) {
  163. return [].concat(...array);
  164. }
  165. },{}],3:[function(require,module,exports){
  166. /**
  167. * @license MIT
  168. * doc-path <https://github.com/mrodrig/doc-path>
  169. * Copyright (c) 2015-present, Michael Rodrigues.
  170. */
  171. "use strict";function evaluatePath(t,r){if(!t)return null;let{dotIndex:e,key:a,remaining:i}=state(r);return e>=0&&!t[r]?Array.isArray(t[a])?t[a].map(t=>evaluatePath(t,i)):evaluatePath(t[a],i):Array.isArray(t)?t.map(t=>evaluatePath(t,r)):t[r]}function setPath(t,r,e){if(!t)throw new Error("No object was provided.");if(!r)throw new Error("No keyPath was provided.");return r.startsWith("__proto__")||r.startsWith("constructor")||r.startsWith("prototype")?t:_sp(t,r,e)}function _sp(t,r,e){let{dotIndex:a,key:i,remaining:s}=state(r);if(a>=0){if(!t[i]&&Array.isArray(t))return t.forEach(t=>_sp(t,r,e));t[i]||(t[i]={}),_sp(t[i],s,e)}else{if(Array.isArray(t))return t.forEach(t=>_sp(t,s,e));t[r]=e}return t}function state(t){let r=t.indexOf(".");return{dotIndex:r,key:t.slice(0,r>=0?r:void 0),remaining:t.slice(r+1)}}module.exports={evaluatePath:evaluatePath,setPath:setPath};
  172. },{}],4:[function(require,module,exports){
  173. module.exports={
  174. "errors" : {
  175. "callbackRequired": "A callback is required!",
  176. "optionsRequired": "Options were not passed and are required.",
  177. "json2csv": {
  178. "cannotCallOn": "Cannot call json2csv on ",
  179. "dataCheckFailure": "Data provided was not an array of documents.",
  180. "notSameSchema": "Not all documents have the same schema."
  181. },
  182. "csv2json": {
  183. "cannotCallOn": "Cannot call csv2json on ",
  184. "dataCheckFailure": "CSV is not a string."
  185. }
  186. },
  187. "defaultOptions" : {
  188. "delimiter" : {
  189. "field" : ",",
  190. "wrap" : "\"",
  191. "eol" : "\n"
  192. },
  193. "excelBOM": false,
  194. "prependHeader" : true,
  195. "trimHeaderFields": false,
  196. "trimFieldValues" : false,
  197. "sortHeader" : false,
  198. "parseCsvNumbers" : false,
  199. "keys" : null,
  200. "checkSchemaDifferences": false,
  201. "expandArrayObjects": false,
  202. "unwindArrays": false,
  203. "useDateIso8601Format": false,
  204. "useLocaleFormat": false
  205. },
  206. "values" : {
  207. "excelBOM": "\ufeff"
  208. }
  209. }
  210. },{}],5:[function(require,module,exports){
  211. 'use strict';
  212. let {Json2Csv} = require('./json2csv'), // Require our json-2-csv code
  213. {Csv2Json} = require('./csv2json'), // Require our csv-2-json code
  214. utils = require('./utils');
  215. module.exports = {
  216. json2csv: (data, callback, options) => convert(Json2Csv, data, callback, options),
  217. csv2json: (data, callback, options) => convert(Csv2Json, data, callback, options),
  218. json2csvAsync: (json, options) => convertAsync(Json2Csv, json, options),
  219. csv2jsonAsync: (csv, options) => convertAsync(Csv2Json, csv, options),
  220. /**
  221. * @deprecated Since v3.0.0
  222. */
  223. json2csvPromisified: (json, options) => deprecatedAsync(Json2Csv, 'json2csvPromisified()', 'json2csvAsync()', json, options),
  224. /**
  225. * @deprecated Since v3.0.0
  226. */
  227. csv2jsonPromisified: (csv, options) => deprecatedAsync(Csv2Json, 'csv2jsonPromisified()', 'csv2jsonAsync()', csv, options)
  228. };
  229. /**
  230. * Abstracted converter function for json2csv and csv2json functionality
  231. * Takes in the converter to be used (either Json2Csv or Csv2Json)
  232. * @param converter
  233. * @param data
  234. * @param callback
  235. * @param options
  236. */
  237. function convert(converter, data, callback, options) {
  238. return utils.convert({
  239. data: data,
  240. callback,
  241. options,
  242. converter: converter
  243. });
  244. }
  245. /**
  246. * Simple way to promisify a callback version of json2csv or csv2json
  247. * @param converter
  248. * @param data
  249. * @param options
  250. * @returns {Promise<any>}
  251. */
  252. function convertAsync(converter, data, options) {
  253. return new Promise((resolve, reject) => convert(converter, data, (err, data) => {
  254. if (err) {
  255. return reject(err);
  256. }
  257. return resolve(data);
  258. }, options));
  259. }
  260. /**
  261. * Simple way to provide a deprecation warning for previous promisified versions
  262. * of module functionality.
  263. * @param converter
  264. * @param deprecatedName
  265. * @param replacementName
  266. * @param data
  267. * @param options
  268. * @returns {Promise<any>}
  269. */
  270. function deprecatedAsync(converter, deprecatedName, replacementName, data, options) {
  271. console.warn('WARNING: ' + deprecatedName + ' is deprecated and will be removed soon. Please use ' + replacementName + ' instead.');
  272. return convertAsync(converter, data, options);
  273. }
  274. },{"./csv2json":6,"./json2csv":7,"./utils":8}],6:[function(require,module,exports){
  275. 'use strict';
  276. let path = require('doc-path'),
  277. constants = require('./constants.json'),
  278. utils = require('./utils');
  279. const Csv2Json = function(options) {
  280. const escapedWrapDelimiterRegex = new RegExp(options.delimiter.wrap + options.delimiter.wrap, 'g'),
  281. excelBOMRegex = new RegExp('^' + constants.values.excelBOM);
  282. /**
  283. * Trims the header key, if specified by the user via the provided options
  284. * @param headerKey
  285. * @returns {*}
  286. */
  287. function processHeaderKey(headerKey) {
  288. headerKey = removeWrapDelimitersFromValue(headerKey);
  289. if (options.trimHeaderFields) {
  290. return headerKey.split('.')
  291. .map((component) => component.trim())
  292. .join('.');
  293. }
  294. return headerKey;
  295. }
  296. /**
  297. * Generate the JSON heading from the CSV
  298. * @param lines {String[]} csv lines split by EOL delimiter
  299. * @returns {*}
  300. */
  301. function retrieveHeading(lines) {
  302. let params = {lines},
  303. // Generate and return the heading keys
  304. headerRow = params.lines[0];
  305. params.headerFields = headerRow.map((headerKey, index) => ({
  306. value: processHeaderKey(headerKey),
  307. index: index
  308. }));
  309. // If the user provided keys, filter the generated keys to just the user provided keys so we also have the key index
  310. if (options.keys) {
  311. params.headerFields = params.headerFields.filter((headerKey) => options.keys.includes(headerKey.value));
  312. }
  313. return params;
  314. }
  315. /**
  316. * Splits the lines of the CSV string by the EOL delimiter and resolves and array of strings (lines)
  317. * @param csv
  318. * @returns {Promise.<String[]>}
  319. */
  320. function splitCsvLines(csv) {
  321. return Promise.resolve(splitLines(csv));
  322. }
  323. /**
  324. * Removes the Excel BOM value, if specified by the options object
  325. * @param csv
  326. * @returns {Promise.<String>}
  327. */
  328. function stripExcelBOM(csv) {
  329. if (options.excelBOM) {
  330. return Promise.resolve(csv.replace(excelBOMRegex, ''));
  331. }
  332. return Promise.resolve(csv);
  333. }
  334. /**
  335. * Helper function that splits a line so that we can handle wrapped fields
  336. * @param csv
  337. */
  338. function splitLines(csv) {
  339. // Parse out the line...
  340. let lines = [],
  341. splitLine = [],
  342. character,
  343. charBefore,
  344. charAfter,
  345. nextNChar,
  346. lastCharacterIndex = csv.length - 1,
  347. eolDelimiterLength = options.delimiter.eol.length,
  348. stateVariables = {
  349. insideWrapDelimiter: false,
  350. parsingValue: true,
  351. justParsedDoubleQuote: false,
  352. startIndex: 0
  353. },
  354. index = 0;
  355. // Loop through each character in the line to identify where to split the values
  356. while (index < csv.length) {
  357. // Current character
  358. character = csv[index];
  359. // Previous character
  360. charBefore = index ? csv[index - 1] : '';
  361. // Next character
  362. charAfter = index < lastCharacterIndex ? csv[index + 1] : '';
  363. // Next n characters, including the current character, where n = length(EOL delimiter)
  364. // This allows for the checking of an EOL delimiter when if it is more than a single character (eg. '\r\n')
  365. nextNChar = utils.getNCharacters(csv, index, eolDelimiterLength);
  366. if ((nextNChar === options.delimiter.eol && !stateVariables.insideWrapDelimiter ||
  367. index === lastCharacterIndex) && charBefore === options.delimiter.field) {
  368. // If we reached an EOL delimiter or the end of the csv and the previous character is a field delimiter...
  369. // If the start index is the current index (and since the previous character is a comma),
  370. // then the value being parsed is an empty value accordingly, add an empty string
  371. if (nextNChar === options.delimiter.eol && stateVariables.startIndex === index) {
  372. splitLine.push('');
  373. } else if (character === options.delimiter.field) {
  374. // If we reached the end of the CSV, there's no new line, and the current character is a comma
  375. // then add an empty string for the current value
  376. splitLine.push('');
  377. } else {
  378. // Otherwise, there's a valid value, and the start index isn't the current index, grab the whole value
  379. splitLine.push(csv.substr(stateVariables.startIndex));
  380. }
  381. // Since the last character is a comma, there's still an additional implied field value trailing the comma.
  382. // Since this value is empty, we push an extra empty value
  383. splitLine.push('');
  384. // Finally, push the split line values into the lines array and clear the split line
  385. lines.push(splitLine);
  386. splitLine = [];
  387. stateVariables.startIndex = index + eolDelimiterLength;
  388. stateVariables.parsingValue = true;
  389. stateVariables.insideWrapDelimiter = charAfter === options.delimiter.wrap;
  390. } else if (index === lastCharacterIndex && character === options.delimiter.field) {
  391. // If we reach the end of the CSV and the current character is a field delimiter
  392. // Parse the previously seen value and add it to the line
  393. let parsedValue = csv.substring(stateVariables.startIndex, index);
  394. splitLine.push(parsedValue);
  395. // Then add an empty string to the line since the last character being a field delimiter indicates an empty field
  396. splitLine.push('');
  397. lines.push(splitLine);
  398. } else if (index === lastCharacterIndex || nextNChar === options.delimiter.eol &&
  399. // if we aren't inside wrap delimiters or if we are but the character before was a wrap delimiter and we didn't just see two
  400. (!stateVariables.insideWrapDelimiter ||
  401. stateVariables.insideWrapDelimiter && charBefore === options.delimiter.wrap && !stateVariables.justParsedDoubleQuote)) {
  402. // Otherwise if we reached the end of the line or csv (and current character is not a field delimiter)
  403. let toIndex = index !== lastCharacterIndex || charBefore === options.delimiter.wrap ? index : undefined;
  404. // Retrieve the remaining value and add it to the split line list of values
  405. splitLine.push(csv.substring(stateVariables.startIndex, toIndex));
  406. // Finally, push the split line values into the lines array and clear the split line
  407. lines.push(splitLine);
  408. splitLine = [];
  409. stateVariables.startIndex = index + eolDelimiterLength;
  410. stateVariables.parsingValue = true;
  411. stateVariables.insideWrapDelimiter = charAfter === options.delimiter.wrap;
  412. } else if ((charBefore !== options.delimiter.wrap || stateVariables.justParsedDoubleQuote && charBefore === options.delimiter.wrap) &&
  413. character === options.delimiter.wrap && utils.getNCharacters(csv, index + 1, eolDelimiterLength) === options.delimiter.eol) {
  414. // If we reach a wrap which is not preceded by a wrap delim and the next character is an EOL delim (ie. *"\n)
  415. stateVariables.insideWrapDelimiter = false;
  416. stateVariables.parsingValue = false;
  417. // Next iteration will substring, add the value to the line, and push the line onto the array of lines
  418. } else if (character === options.delimiter.wrap && (index === 0 || utils.getNCharacters(csv, index - eolDelimiterLength, eolDelimiterLength) === options.delimiter.eol)) {
  419. // If the line starts with a wrap delimiter (ie. "*)
  420. stateVariables.insideWrapDelimiter = true;
  421. stateVariables.parsingValue = true;
  422. stateVariables.startIndex = index;
  423. } else if (character === options.delimiter.wrap && charAfter === options.delimiter.field) {
  424. // If we reached a wrap delimiter with a field delimiter after it (ie. *",)
  425. splitLine.push(csv.substring(stateVariables.startIndex, index + 1));
  426. stateVariables.startIndex = index + 2; // next value starts after the field delimiter
  427. stateVariables.insideWrapDelimiter = false;
  428. stateVariables.parsingValue = false;
  429. } else if (character === options.delimiter.wrap && charBefore === options.delimiter.field &&
  430. !stateVariables.insideWrapDelimiter && !stateVariables.parsingValue) {
  431. // If we reached a wrap delimiter after a comma and we aren't inside a wrap delimiter
  432. stateVariables.startIndex = index;
  433. stateVariables.insideWrapDelimiter = true;
  434. stateVariables.parsingValue = true;
  435. } else if (character === options.delimiter.wrap && charBefore === options.delimiter.field &&
  436. !stateVariables.insideWrapDelimiter && stateVariables.parsingValue) {
  437. // If we reached a wrap delimiter with a field delimiter after it (ie. ,"*)
  438. splitLine.push(csv.substring(stateVariables.startIndex, index - 1));
  439. stateVariables.insideWrapDelimiter = true;
  440. stateVariables.parsingValue = true;
  441. stateVariables.startIndex = index;
  442. } else if (character === options.delimiter.wrap && charAfter === options.delimiter.wrap) {
  443. // If we run into an escaped quote (ie. "") skip past the second quote
  444. index += 2;
  445. stateVariables.justParsedDoubleQuote = true;
  446. continue;
  447. } else if (character === options.delimiter.field && charBefore !== options.delimiter.wrap &&
  448. charAfter !== options.delimiter.wrap && !stateVariables.insideWrapDelimiter &&
  449. stateVariables.parsingValue) {
  450. // If we reached a field delimiter and are not inside the wrap delimiters (ie. *,*)
  451. splitLine.push(csv.substring(stateVariables.startIndex, index));
  452. stateVariables.startIndex = index + 1;
  453. } else if (character === options.delimiter.field && charBefore === options.delimiter.wrap &&
  454. charAfter !== options.delimiter.wrap && !stateVariables.parsingValue) {
  455. // If we reached a field delimiter, the previous character was a wrap delimiter, and the
  456. // next character is not a wrap delimiter (ie. ",*)
  457. stateVariables.insideWrapDelimiter = false;
  458. stateVariables.parsingValue = true;
  459. stateVariables.startIndex = index + 1;
  460. }
  461. // Otherwise increment to the next character
  462. index++;
  463. // Reset the double quote state variable
  464. stateVariables.justParsedDoubleQuote = false;
  465. }
  466. return lines;
  467. }
  468. /**
  469. * Retrieves the record lines from the split CSV lines and sets it on the params object
  470. * @param params
  471. * @returns {*}
  472. */
  473. function retrieveRecordLines(params) {
  474. params.recordLines = params.lines.splice(1); // All lines except for the header line
  475. return params;
  476. }
  477. /**
  478. * Retrieves the value for the record from the line at the provided key.
  479. * @param line {String[]} split line values for the record
  480. * @param key {Object} {index: Number, value: String}
  481. */
  482. function retrieveRecordValueFromLine(line, key) {
  483. // If there is a value at the key's index, use it; otherwise, null
  484. let value = line[key.index];
  485. // Perform any necessary value conversions on the record value
  486. return processRecordValue(value);
  487. }
  488. /**
  489. * Processes the record's value by parsing the data to ensure the CSV is
  490. * converted to the JSON that created it.
  491. * @param fieldValue {String}
  492. * @returns {*}
  493. */
  494. function processRecordValue(fieldValue) {
  495. // If the value is an array representation, convert it
  496. let parsedJson = parseValue(fieldValue);
  497. // If parsedJson is anything aside from an error, then we want to use the parsed value
  498. // This allows us to interpret values like 'null' --> null, 'false' --> false
  499. if (!utils.isError(parsedJson) && !utils.isInvalid(parsedJson)) {
  500. fieldValue = parsedJson;
  501. } else if (fieldValue === 'undefined') {
  502. fieldValue = undefined;
  503. }
  504. return fieldValue;
  505. }
  506. /**
  507. * Trims the record value, if specified by the user via the options object
  508. * @param fieldValue
  509. * @returns {String|null}
  510. */
  511. function trimRecordValue(fieldValue) {
  512. if (options.trimFieldValues && !utils.isNull(fieldValue)) {
  513. return fieldValue.trim();
  514. }
  515. return fieldValue;
  516. }
  517. /**
  518. * Create a JSON document with the given keys (designated by the CSV header)
  519. * and the values (from the given line)
  520. * @param keys String[]
  521. * @param line String
  522. * @returns {Object} created json document
  523. */
  524. function createDocument(keys, line) {
  525. // Reduce the keys into a JSON document representing the given line
  526. return keys.reduce((document, key) => {
  527. // If there is a value at the key's index in the line, set the value; otherwise null
  528. let value = retrieveRecordValueFromLine(line, key);
  529. // Otherwise add the key and value to the document
  530. return path.setPath(document, key.value, value);
  531. }, {});
  532. }
  533. /**
  534. * Removes the outermost wrap delimiters from a value, if they are present
  535. * Otherwise, the non-wrapped value is returned as is
  536. * @param fieldValue
  537. * @returns {String}
  538. */
  539. function removeWrapDelimitersFromValue(fieldValue) {
  540. let firstChar = fieldValue[0],
  541. lastIndex = fieldValue.length - 1,
  542. lastChar = fieldValue[lastIndex];
  543. // If the field starts and ends with a wrap delimiter
  544. if (firstChar === options.delimiter.wrap && lastChar === options.delimiter.wrap) {
  545. return fieldValue.substr(1, lastIndex - 1);
  546. }
  547. return fieldValue;
  548. }
  549. /**
  550. * Unescapes wrap delimiters by replacing duplicates with a single (eg. "" -> ")
  551. * This is done in order to parse RFC 4180 compliant CSV back to JSON
  552. * @param fieldValue
  553. * @returns {String}
  554. */
  555. function unescapeWrapDelimiterInField(fieldValue) {
  556. return fieldValue.replace(escapedWrapDelimiterRegex, options.delimiter.wrap);
  557. }
  558. /**
  559. * Main helper function to convert the CSV to the JSON document array
  560. * @param params {Object} {lines: [String], callback: Function}
  561. * @returns {Array}
  562. */
  563. function transformRecordLines(params) {
  564. params.json = params.recordLines.reduce((generatedJsonObjects, line) => { // For each line, create the document and add it to the array of documents
  565. line = line.map((fieldValue) => {
  566. // Perform the necessary operations on each line
  567. fieldValue = removeWrapDelimitersFromValue(fieldValue);
  568. fieldValue = unescapeWrapDelimiterInField(fieldValue);
  569. fieldValue = trimRecordValue(fieldValue);
  570. return fieldValue;
  571. });
  572. let generatedDocument = createDocument(params.headerFields, line);
  573. return generatedJsonObjects.concat(generatedDocument);
  574. }, []);
  575. return params;
  576. }
  577. /**
  578. * Attempts to parse the provided value. If it is not parsable, then an error is returned
  579. * @param value
  580. * @returns {*}
  581. */
  582. function parseValue(value) {
  583. try {
  584. if (utils.isStringRepresentation(value, options) && !utils.isDateRepresentation(value)) {
  585. return value;
  586. }
  587. let parsedJson = JSON.parse(value);
  588. // If the parsed value is an array, then we also need to trim record values, if specified
  589. if (Array.isArray(parsedJson)) {
  590. return parsedJson.map(trimRecordValue);
  591. }
  592. return parsedJson;
  593. } catch (err) {
  594. return err;
  595. }
  596. }
  597. /**
  598. * Internally exported csv2json function
  599. * Takes options as a document, data as a CSV string, and a callback that will be used to report the results
  600. * @param data String csv string
  601. * @param callback Function callback function
  602. */
  603. function convert(data, callback) {
  604. // Split the CSV into lines using the specified EOL option
  605. // validateCsv(data, callback)
  606. // .then(stripExcelBOM)
  607. stripExcelBOM(data)
  608. .then(splitCsvLines)
  609. .then(retrieveHeading) // Retrieve the headings from the CSV, unless the user specified the keys
  610. .then(retrieveRecordLines) // Retrieve the record lines from the CSV
  611. .then(transformRecordLines) // Retrieve the JSON document array
  612. .then((params) => callback(null, params.json)) // Send the data back to the caller
  613. .catch(callback);
  614. }
  615. return {
  616. convert,
  617. validationFn: utils.isString,
  618. validationMessages: constants.errors.csv2json
  619. };
  620. };
  621. module.exports = { Csv2Json };
  622. },{"./constants.json":4,"./utils":8,"doc-path":3}],7:[function(require,module,exports){
  623. 'use strict';
  624. let path = require('doc-path'),
  625. deeks = require('deeks'),
  626. constants = require('./constants.json'),
  627. utils = require('./utils');
  628. const Json2Csv = function(options) {
  629. const wrapDelimiterCheckRegex = new RegExp(options.delimiter.wrap, 'g'),
  630. crlfSearchRegex = /\r?\n|\r/,
  631. expandingWithoutUnwinding = options.expandArrayObjects && !options.unwindArrays,
  632. deeksOptions = {
  633. expandArrayObjects: expandingWithoutUnwinding,
  634. ignoreEmptyArraysWhenExpanding: expandingWithoutUnwinding
  635. };
  636. /** HEADER FIELD FUNCTIONS **/
  637. /**
  638. * Returns the list of data field names of all documents in the provided list
  639. * @param data {Array<Object>} Data to be converted
  640. * @returns {Promise.<Array[String]>}
  641. */
  642. function getFieldNameList(data) {
  643. // If keys weren't specified, then we'll use the list of keys generated by the deeks module
  644. return Promise.resolve(deeks.deepKeysFromList(data, deeksOptions));
  645. }
  646. /**
  647. * Processes the schemas by checking for schema differences, if so desired.
  648. * If schema differences are not to be checked, then it resolves the unique
  649. * list of field names.
  650. * @param documentSchemas
  651. * @returns {Promise.<Array[String]>}
  652. */
  653. function processSchemas(documentSchemas) {
  654. // If the user wants to check for the same schema (regardless of schema ordering)
  655. if (options.checkSchemaDifferences) {
  656. return checkSchemaDifferences(documentSchemas);
  657. } else {
  658. // Otherwise, we do not care if the schemas are different, so we should get the unique list of keys
  659. let uniqueFieldNames = utils.unique(utils.flatten(documentSchemas));
  660. return Promise.resolve(uniqueFieldNames);
  661. }
  662. }
  663. /**
  664. * This function performs the schema difference check, if the user specifies that it should be checked.
  665. * If there are no field names, then there are no differences.
  666. * Otherwise, we get the first schema and the remaining list of schemas
  667. * @param documentSchemas
  668. * @returns {*}
  669. */
  670. function checkSchemaDifferences(documentSchemas) {
  671. // have multiple documents - ensure only one schema (regardless of field ordering)
  672. let firstDocSchema = documentSchemas[0],
  673. restOfDocumentSchemas = documentSchemas.slice(1),
  674. schemaDifferences = computeNumberOfSchemaDifferences(firstDocSchema, restOfDocumentSchemas);
  675. // If there are schema inconsistencies, throw a schema not the same error
  676. if (schemaDifferences) {
  677. return Promise.reject(new Error(constants.errors.json2csv.notSameSchema));
  678. }
  679. return Promise.resolve(firstDocSchema);
  680. }
  681. /**
  682. * Computes the number of schema differences
  683. * @param firstDocSchema
  684. * @param restOfDocumentSchemas
  685. * @returns {*}
  686. */
  687. function computeNumberOfSchemaDifferences(firstDocSchema, restOfDocumentSchemas) {
  688. return restOfDocumentSchemas.reduce((schemaDifferences, documentSchema) => {
  689. // If there is a difference between the schemas, increment the counter of schema inconsistencies
  690. let numberOfDifferences = utils.computeSchemaDifferences(firstDocSchema, documentSchema).length;
  691. return numberOfDifferences > 0
  692. ? schemaDifferences + 1
  693. : schemaDifferences;
  694. }, 0);
  695. }
  696. /**
  697. * If so specified, this sorts the header field names alphabetically
  698. * @param fieldNames {Array<String>}
  699. * @returns {Array<String>} sorted field names, or unsorted if sorting not specified
  700. */
  701. function sortHeaderFields(fieldNames) {
  702. if (options.sortHeader) {
  703. return fieldNames.sort();
  704. }
  705. return fieldNames;
  706. }
  707. /**
  708. * Trims the header fields, if the user desires them to be trimmed.
  709. * @param params
  710. * @returns {*}
  711. */
  712. function trimHeaderFields(params) {
  713. if (options.trimHeaderFields) {
  714. params.headerFields = params.headerFields.map((field) => field.split('.')
  715. .map((component) => component.trim())
  716. .join('.')
  717. );
  718. }
  719. return params;
  720. }
  721. /**
  722. * Wrap the headings, if desired by the user.
  723. * @param params
  724. * @returns {*}
  725. */
  726. function wrapHeaderFields(params) {
  727. // only perform this if we are actually prepending the header
  728. if (options.prependHeader) {
  729. params.headerFields = params.headerFields.map(function(headingKey) {
  730. return wrapFieldValueIfNecessary(headingKey);
  731. });
  732. }
  733. return params;
  734. }
  735. /**
  736. * Generates the CSV header string by joining the headerFields by the field delimiter
  737. * @param params
  738. * @returns {*}
  739. */
  740. function generateCsvHeader(params) {
  741. params.header = params.headerFields
  742. .map(function(field) {
  743. const headerKey = options.fieldTitleMap[field] ? options.fieldTitleMap[field] : field;
  744. return wrapFieldValueIfNecessary(headerKey);
  745. })
  746. .join(options.delimiter.field);
  747. return params;
  748. }
  749. /**
  750. * Retrieve the headings for all documents and return it.
  751. * This checks that all documents have the same schema.
  752. * @param data
  753. * @returns {Promise}
  754. */
  755. function retrieveHeaderFields(data) {
  756. if (options.keys) {
  757. options.keys = options.keys.map((key) => {
  758. if (utils.isObject(key) && key.field) {
  759. options.fieldTitleMap[key.field] = key.title || key.field;
  760. return key.field;
  761. }
  762. return key;
  763. });
  764. }
  765. if (options.keys && !options.unwindArrays) {
  766. return Promise.resolve(options.keys)
  767. .then(sortHeaderFields);
  768. }
  769. return getFieldNameList(data)
  770. .then(processSchemas)
  771. .then(sortHeaderFields);
  772. }
  773. /** RECORD FIELD FUNCTIONS **/
  774. /**
  775. * Unwinds objects in arrays within record objects if the user specifies the
  776. * expandArrayObjects option. If not specified, this passes the params
  777. * argument through to the next function in the promise chain.
  778. * @param params {Object}
  779. * @param finalPass {boolean} Used to trigger one last pass to ensure no more arrays need to be expanded
  780. * @returns {Promise}
  781. */
  782. function unwindRecordsIfNecessary(params, finalPass = false) {
  783. if (options.unwindArrays) {
  784. const originalRecordsLength = params.records.length;
  785. // Unwind each of the documents at the given headerField
  786. params.headerFields.forEach((headerField) => {
  787. params.records = utils.unwind(params.records, headerField);
  788. });
  789. return retrieveHeaderFields(params.records)
  790. .then((headerFields) => {
  791. params.headerFields = headerFields;
  792. // If we were able to unwind more arrays, then try unwinding again...
  793. if (originalRecordsLength !== params.records.length) {
  794. return unwindRecordsIfNecessary(params);
  795. }
  796. // Otherwise, we didn't unwind any additional arrays, so continue...
  797. // Run a final time in case the earlier unwinding exposed additional
  798. // arrays to unwind...
  799. if (!finalPass) {
  800. return unwindRecordsIfNecessary(params, true);
  801. }
  802. // If keys were provided, set the headerFields to the provided keys:
  803. if (options.keys) {
  804. params.headerFields = options.keys;
  805. }
  806. return params;
  807. });
  808. }
  809. return params;
  810. }
  811. /**
  812. * Main function which handles the processing of a record, or document to be converted to CSV format
  813. * This function specifies and performs the necessary operations in the necessary order
  814. * in order to obtain the data and convert it to CSV form while maintaining RFC 4180 compliance.
  815. * * Order of operations:
  816. * - Get fields from provided key list (as array of actual values)
  817. * - Convert the values to csv/string representation [possible option here for custom converters?]
  818. * - Trim fields
  819. * - Determine if they need to be wrapped (& wrap if necessary)
  820. * - Combine values for each line (by joining by field delimiter)
  821. * @param params
  822. * @returns {*}
  823. */
  824. function processRecords(params) {
  825. params.records = params.records.map((record) => {
  826. // Retrieve data for each of the headerFields from this record
  827. let recordFieldData = retrieveRecordFieldData(record, params.headerFields),
  828. // Process the data in this record and return the
  829. processedRecordData = recordFieldData.map((fieldValue) => {
  830. fieldValue = trimRecordFieldValue(fieldValue);
  831. fieldValue = recordFieldValueToString(fieldValue);
  832. fieldValue = wrapFieldValueIfNecessary(fieldValue);
  833. return fieldValue;
  834. });
  835. // Join the record data by the field delimiter
  836. return generateCsvRowFromRecord(processedRecordData);
  837. }).join(options.delimiter.eol);
  838. return params;
  839. }
  840. /**
  841. * Helper function intended to process *just* array values when the expandArrayObjects setting is set to true
  842. * @param recordFieldValue
  843. * @returns {*} processed array value
  844. */
  845. function processRecordFieldDataForExpandedArrayObject(recordFieldValue) {
  846. let filteredRecordFieldValue = utils.removeEmptyFields(recordFieldValue);
  847. // If we have an array and it's either empty of full of empty values, then use an empty value representation
  848. if (!recordFieldValue.length || !filteredRecordFieldValue.length) {
  849. return options.emptyFieldValue || '';
  850. } else if (filteredRecordFieldValue.length === 1) {
  851. // Otherwise, we have an array of actual values...
  852. // Since we are expanding array objects, we will want to key in on values of objects.
  853. return filteredRecordFieldValue[0]; // Extract the single value in the array
  854. }
  855. return recordFieldValue;
  856. }
  857. /**
  858. * Gets all field values from a particular record for the given list of fields
  859. * @param record
  860. * @param fields
  861. * @returns {Array}
  862. */
  863. function retrieveRecordFieldData(record, fields) {
  864. let recordValues = [];
  865. fields.forEach((field) => {
  866. let recordFieldValue = path.evaluatePath(record, field);
  867. if (!utils.isUndefined(options.emptyFieldValue) && utils.isEmptyField(recordFieldValue)) {
  868. recordFieldValue = options.emptyFieldValue;
  869. } else if (options.expandArrayObjects && Array.isArray(recordFieldValue)) {
  870. recordFieldValue = processRecordFieldDataForExpandedArrayObject(recordFieldValue);
  871. }
  872. recordValues.push(recordFieldValue);
  873. });
  874. return recordValues;
  875. }
  876. /**
  877. * Converts a record field value to its string representation
  878. * @param fieldValue
  879. * @returns {*}
  880. */
  881. function recordFieldValueToString(fieldValue) {
  882. const isDate = utils.isDate(fieldValue); // store to avoid checking twice
  883. if (utils.isNull(fieldValue) || Array.isArray(fieldValue) || utils.isObject(fieldValue) && !isDate) {
  884. return JSON.stringify(fieldValue);
  885. } else if (utils.isUndefined(fieldValue)) {
  886. return 'undefined';
  887. } else if (isDate && options.useDateIso8601Format) {
  888. return fieldValue.toISOString();
  889. } else {
  890. return !options.useLocaleFormat ? fieldValue.toString() : fieldValue.toLocaleString();
  891. }
  892. }
  893. /**
  894. * Trims the record field value, if specified by the user's provided options
  895. * @param fieldValue
  896. * @returns {*}
  897. */
  898. function trimRecordFieldValue(fieldValue) {
  899. if (options.trimFieldValues) {
  900. if (Array.isArray(fieldValue)) {
  901. return fieldValue.map(trimRecordFieldValue);
  902. } else if (utils.isString(fieldValue)) {
  903. return fieldValue.trim();
  904. }
  905. return fieldValue;
  906. }
  907. return fieldValue;
  908. }
  909. /**
  910. * Escapes quotation marks in the field value, if necessary, and appropriately
  911. * wraps the record field value if it contains a comma (field delimiter),
  912. * quotation mark (wrap delimiter), or a line break (CRLF)
  913. * @param fieldValue
  914. * @returns {*}
  915. */
  916. function wrapFieldValueIfNecessary(fieldValue) {
  917. const wrapDelimiter = options.delimiter.wrap;
  918. // eg. includes quotation marks (default delimiter)
  919. if (fieldValue.includes(options.delimiter.wrap)) {
  920. // add an additional quotation mark before each quotation mark appearing in the field value
  921. fieldValue = fieldValue.replace(wrapDelimiterCheckRegex, wrapDelimiter + wrapDelimiter);
  922. }
  923. // if the field contains a comma (field delimiter), quotation mark (wrap delimiter), line break, or CRLF
  924. // then enclose it in quotation marks (wrap delimiter)
  925. if (fieldValue.includes(options.delimiter.field) ||
  926. fieldValue.includes(options.delimiter.wrap) ||
  927. fieldValue.match(crlfSearchRegex)) {
  928. // wrap the field's value in a wrap delimiter (quotation marks by default)
  929. fieldValue = wrapDelimiter + fieldValue + wrapDelimiter;
  930. }
  931. return fieldValue;
  932. }
  933. /**
  934. * Generates the CSV record string by joining the field values together by the field delimiter
  935. * @param recordFieldValues
  936. */
  937. function generateCsvRowFromRecord(recordFieldValues) {
  938. return recordFieldValues.join(options.delimiter.field);
  939. }
  940. /** CSV COMPONENT COMBINER/FINAL PROCESSOR **/
  941. /**
  942. * Performs the final CSV construction by combining the fields in the appropriate
  943. * order depending on the provided options values and sends the generated CSV
  944. * back to the user
  945. * @param params
  946. */
  947. function generateCsvFromComponents(params) {
  948. let header = params.header,
  949. records = params.records,
  950. // If we are prepending the header, then add an EOL, otherwise just return the records
  951. csv = (options.excelBOM ? constants.values.excelBOM : '') +
  952. (options.prependHeader ? header + options.delimiter.eol : '') +
  953. records;
  954. return params.callback(null, csv);
  955. }
  956. /** MAIN CONVERTER FUNCTION **/
  957. /**
  958. * Internally exported json2csv function
  959. * Takes data as either a document or array of documents and a callback that will be used to report the results
  960. * @param data {Object|Array<Object>} documents to be converted to csv
  961. * @param callback {Function} callback function
  962. */
  963. function convert(data, callback) {
  964. // Single document, not an array
  965. if (utils.isObject(data) && !data.length) {
  966. data = [data]; // Convert to an array of the given document
  967. }
  968. // Retrieve the heading and then generate the CSV with the keys that are identified
  969. retrieveHeaderFields(data)
  970. .then((headerFields) => ({
  971. headerFields,
  972. callback,
  973. records: data
  974. }))
  975. .then(unwindRecordsIfNecessary)
  976. .then(processRecords)
  977. .then(wrapHeaderFields)
  978. .then(trimHeaderFields)
  979. .then(generateCsvHeader)
  980. .then(generateCsvFromComponents)
  981. .catch(callback);
  982. }
  983. return {
  984. convert,
  985. validationFn: utils.isObject,
  986. validationMessages: constants.errors.json2csv
  987. };
  988. };
  989. module.exports = { Json2Csv };
  990. },{"./constants.json":4,"./utils":8,"deeks":1,"doc-path":3}],8:[function(require,module,exports){
  991. 'use strict';
  992. let path = require('doc-path'),
  993. constants = require('./constants.json');
  994. const dateStringRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/,
  995. MAX_ARRAY_LENGTH = 100000;
  996. module.exports = {
  997. isStringRepresentation,
  998. isDateRepresentation,
  999. computeSchemaDifferences,
  1000. deepCopy,
  1001. convert,
  1002. isEmptyField,
  1003. removeEmptyFields,
  1004. getNCharacters,
  1005. unwind,
  1006. isInvalid,
  1007. // underscore replacements:
  1008. isString,
  1009. isNull,
  1010. isError,
  1011. isDate,
  1012. isUndefined,
  1013. isObject,
  1014. unique,
  1015. flatten
  1016. };
  1017. /**
  1018. * Build the options to be passed to the appropriate function
  1019. * If a user does not provide custom options, then we use our default
  1020. * If options are provided, then we set each valid key that was passed
  1021. * @param opts {Object} options object
  1022. * @return {Object} options object
  1023. */
  1024. function buildOptions(opts) {
  1025. opts = {...constants.defaultOptions, ...opts || {}};
  1026. opts.fieldTitleMap = new Map();
  1027. // Copy the delimiter fields over since the spread operator does a shallow copy
  1028. opts.delimiter.field = opts.delimiter.field || constants.defaultOptions.delimiter.field;
  1029. opts.delimiter.wrap = opts.delimiter.wrap || constants.defaultOptions.delimiter.wrap;
  1030. opts.delimiter.eol = opts.delimiter.eol || constants.defaultOptions.delimiter.eol;
  1031. // Otherwise, send the options back
  1032. return opts;
  1033. }
  1034. /**
  1035. * When promisified, the callback and options argument ordering is swapped, so
  1036. * this function is intended to determine which argument is which and return
  1037. * them in the correct order
  1038. * @param arg1 {Object|Function} options or callback
  1039. * @param arg2 {Object|Function} options or callback
  1040. */
  1041. function parseArguments(arg1, arg2) {
  1042. // If this was promisified (callback and opts are swapped) then fix the argument order.
  1043. if (isObject(arg1) && !isFunction(arg1)) {
  1044. return {
  1045. options: arg1,
  1046. callback: arg2
  1047. };
  1048. }
  1049. // Regular ordering where the callback is provided before the options object
  1050. return {
  1051. options: arg2,
  1052. callback: arg1
  1053. };
  1054. }
  1055. /**
  1056. * Validates the parameters passed in to json2csv and csv2json
  1057. * @param config {Object} of the form: { data: {Any}, callback: {Function}, dataCheckFn: Function, errorMessages: {Object} }
  1058. */
  1059. function validateParameters(config) {
  1060. // If a callback wasn't provided, throw an error
  1061. if (!config.callback) {
  1062. throw new Error(constants.errors.callbackRequired);
  1063. }
  1064. // If we don't receive data, report an error
  1065. if (!config.data) {
  1066. config.callback(new Error(config.errorMessages.cannotCallOn + config.data + '.'));
  1067. return false;
  1068. }
  1069. // The data provided data does not meet the type check requirement
  1070. if (!config.dataCheckFn(config.data)) {
  1071. config.callback(new Error(config.errorMessages.dataCheckFailure));
  1072. return false;
  1073. }
  1074. // If we didn't hit any known error conditions, then the data is so far determined to be valid
  1075. // Note: json2csv/csv2json may perform additional validity checks on the data
  1076. return true;
  1077. }
  1078. /**
  1079. * Abstracted function to perform the conversion of json-->csv or csv-->json
  1080. * depending on the converter class that is passed via the params object
  1081. * @param params {Object}
  1082. */
  1083. function convert(params) {
  1084. let {options, callback} = parseArguments(params.callback, params.options);
  1085. options = buildOptions(options);
  1086. let converter = new params.converter(options),
  1087. // Validate the parameters before calling the converter's convert function
  1088. valid = validateParameters({
  1089. data: params.data,
  1090. callback,
  1091. errorMessages: converter.validationMessages,
  1092. dataCheckFn: converter.validationFn
  1093. });
  1094. if (valid) converter.convert(params.data, callback);
  1095. }
  1096. /**
  1097. * Utility function to deep copy an object, used by the module tests
  1098. * @param obj
  1099. * @returns {any}
  1100. */
  1101. function deepCopy(obj) {
  1102. return JSON.parse(JSON.stringify(obj));
  1103. }
  1104. /**
  1105. * Helper function that determines whether the provided value is a representation
  1106. * of a string. Given the RFC4180 requirements, that means that the value is
  1107. * wrapped in value wrap delimiters (usually a quotation mark on each side).
  1108. * @param fieldValue
  1109. * @param options
  1110. * @returns {boolean}
  1111. */
  1112. function isStringRepresentation(fieldValue, options) {
  1113. const firstChar = fieldValue[0],
  1114. lastIndex = fieldValue.length - 1,
  1115. lastChar = fieldValue[lastIndex];
  1116. // If the field starts and ends with a wrap delimiter
  1117. return firstChar === options.delimiter.wrap && lastChar === options.delimiter.wrap;
  1118. }
  1119. /**
  1120. * Helper function that determines whether the provided value is a representation
  1121. * of a date.
  1122. * @param fieldValue
  1123. * @returns {boolean}
  1124. */
  1125. function isDateRepresentation(fieldValue) {
  1126. return dateStringRegex.test(fieldValue);
  1127. }
  1128. /**
  1129. * Helper function that determines the schema differences between two objects.
  1130. * @param schemaA
  1131. * @param schemaB
  1132. * @returns {*}
  1133. */
  1134. function computeSchemaDifferences(schemaA, schemaB) {
  1135. return arrayDifference(schemaA, schemaB)
  1136. .concat(arrayDifference(schemaB, schemaA));
  1137. }
  1138. /**
  1139. * Utility function to check if a field is considered empty so that the emptyFieldValue can be used instead
  1140. * @param fieldValue
  1141. * @returns {boolean}
  1142. */
  1143. function isEmptyField(fieldValue) {
  1144. return isUndefined(fieldValue) || isNull(fieldValue) || fieldValue === '';
  1145. }
  1146. /**
  1147. * Helper function that removes empty field values from an array.
  1148. * @param fields
  1149. * @returns {Array}
  1150. */
  1151. function removeEmptyFields(fields) {
  1152. return fields.filter((field) => !isEmptyField(field));
  1153. }
  1154. /**
  1155. * Helper function that retrieves the next n characters from the start index in
  1156. * the string including the character at the start index. This is used to
  1157. * check if are currently at an EOL value, since it could be multiple
  1158. * characters in length (eg. '\r\n')
  1159. * @param str
  1160. * @param start
  1161. * @param n
  1162. * @returns {string}
  1163. */
  1164. function getNCharacters(str, start, n) {
  1165. return str.substring(start, start + n);
  1166. }
  1167. /**
  1168. * The following unwind functionality is a heavily modified version of @edwincen's
  1169. * unwind extension for lodash. Since lodash is a large package to require in,
  1170. * and all of the required functionality was already being imported, either
  1171. * natively or with doc-path, I decided to rewrite the majority of the logic
  1172. * so that an additional dependency would not be required. The original code
  1173. * with the lodash dependency can be found here:
  1174. *
  1175. * https://github.com/edwincen/unwind/blob/master/index.js
  1176. */
  1177. /**
  1178. * Core function that unwinds an item at the provided path
  1179. * @param accumulator {Array<any>}
  1180. * @param item {any}
  1181. * @param fieldPath {String}
  1182. */
  1183. function unwindItem(accumulator, item, fieldPath) {
  1184. const valueToUnwind = path.evaluatePath(item, fieldPath);
  1185. let cloned = deepCopy(item);
  1186. if (Array.isArray(valueToUnwind) && valueToUnwind.length) {
  1187. valueToUnwind.forEach((val) => {
  1188. cloned = deepCopy(item);
  1189. accumulator.push(path.setPath(cloned, fieldPath, val));
  1190. });
  1191. } else if (Array.isArray(valueToUnwind) && valueToUnwind.length === 0) {
  1192. // Push an empty string so the value is empty since there are no values
  1193. path.setPath(cloned, fieldPath, '');
  1194. accumulator.push(cloned);
  1195. } else {
  1196. accumulator.push(cloned);
  1197. }
  1198. }
  1199. /**
  1200. * Main unwind function which takes an array and a field to unwind.
  1201. * @param array {Array<any>}
  1202. * @param field {String}
  1203. * @returns {Array<any>}
  1204. */
  1205. function unwind(array, field) {
  1206. const result = [];
  1207. array.forEach((item) => {
  1208. unwindItem(result, item, field);
  1209. });
  1210. return result;
  1211. }
  1212. /*
  1213. * Helper functions which were created to remove underscorejs from this package.
  1214. */
  1215. function isString(value) {
  1216. return typeof value === 'string';
  1217. }
  1218. function isObject(value) {
  1219. return typeof value === 'object';
  1220. }
  1221. function isFunction(value) {
  1222. return typeof value === 'function';
  1223. }
  1224. function isNull(value) {
  1225. return value === null;
  1226. }
  1227. function isDate(value) {
  1228. return value instanceof Date;
  1229. }
  1230. function isUndefined(value) {
  1231. return typeof value === 'undefined';
  1232. }
  1233. function isError(value) {
  1234. return Object.prototype.toString.call(value) === '[object Error]';
  1235. }
  1236. function arrayDifference(a, b) {
  1237. return a.filter((x) => !b.includes(x));
  1238. }
  1239. function unique(array) {
  1240. return [...new Set(array)];
  1241. }
  1242. function flatten(array) {
  1243. // Node 11+ - use the native array flattening function
  1244. if (array.flat) {
  1245. return array.flat();
  1246. }
  1247. // #167 - allow browsers to flatten very long 200k+ element arrays
  1248. if (array.length > MAX_ARRAY_LENGTH) {
  1249. let safeArray = [];
  1250. for (let a = 0; a < array.length; a += MAX_ARRAY_LENGTH) {
  1251. safeArray = safeArray.concat(...array.slice(a, a + MAX_ARRAY_LENGTH));
  1252. }
  1253. return safeArray;
  1254. }
  1255. return [].concat(...array);
  1256. }
  1257. /**
  1258. * Used to help avoid incorrect values returned by JSON.parse when converting
  1259. * CSV back to JSON, such as '39e1804' which JSON.parse converts to Infinity
  1260. */
  1261. function isInvalid(parsedJson) {
  1262. return parsedJson === Infinity ||
  1263. parsedJson === -Infinity;
  1264. }
  1265. },{"./constants.json":4,"doc-path":3}],9:[function(require,module,exports){
  1266. window.json2csv = require('json-2-csv')
  1267. },{"json-2-csv":5}]},{},[9]);