JSONToCSV.mjs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2018
  4. * @license Apache-2.0
  5. */
  6. import Operation from "../Operation";
  7. import OperationError from "../errors/OperationError";
  8. /**
  9. * JSON to CSV operation
  10. */
  11. class JSONToCSV extends Operation {
  12. /**
  13. * JSONToCSV constructor
  14. */
  15. constructor() {
  16. super();
  17. this.name = "JSON to CSV";
  18. this.module = "Default";
  19. this.description = "Converts JSON data to a CSV based on the definition in RFC 4180.";
  20. this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
  21. this.inputType = "JSON";
  22. this.outputType = "string";
  23. this.args = [
  24. {
  25. name: "Cell delimiter",
  26. type: "binaryShortString",
  27. value: ","
  28. },
  29. {
  30. name: "Row delimiter",
  31. type: "binaryShortString",
  32. value: "\\r\\n"
  33. }
  34. ];
  35. }
  36. /**
  37. * @param {JSON} input
  38. * @param {Object[]} args
  39. * @returns {string}
  40. */
  41. run(input, args) {
  42. const [cellDelim, rowDelim] = args;
  43. // Record values so they don't have to be passed to other functions explicitly
  44. this.cellDelim = cellDelim;
  45. this.rowDelim = rowDelim;
  46. const self = this;
  47. try {
  48. // If the JSON is an array of arrays, this is easy
  49. if (input[0] instanceof Array) {
  50. return input
  51. .map(row => row
  52. .map(self.escapeCellContents.bind(self))
  53. .join(cellDelim)
  54. )
  55. .join(rowDelim) +
  56. rowDelim;
  57. }
  58. // If it's an array of dictionaries...
  59. const header = Object.keys(input[0]);
  60. return header
  61. .map(self.escapeCellContents.bind(self))
  62. .join(cellDelim) +
  63. rowDelim +
  64. input
  65. .map(row => header
  66. .map(h => row[h])
  67. .map(self.escapeCellContents.bind(self))
  68. .join(cellDelim)
  69. )
  70. .join(rowDelim) +
  71. rowDelim;
  72. } catch (err) {
  73. throw new OperationError("Unable to parse JSON to CSV: " + err.toString());
  74. }
  75. }
  76. /**
  77. * Correctly escapes a cell's contents based on the cell and row delimiters.
  78. *
  79. * @param {string} data
  80. * @returns {string}
  81. */
  82. escapeCellContents(data) {
  83. // Double quotes should be doubled up
  84. data = data.replace(/"/g, '""');
  85. // If the cell contains a cell or row delimiter or a double quote, it mut be enclosed in double quotes
  86. if (
  87. data.indexOf(this.cellDelim) >= 0 ||
  88. data.indexOf(this.rowDelim) >= 0 ||
  89. data.indexOf("\n") >= 0 ||
  90. data.indexOf("\r") >= 0 ||
  91. data.indexOf('"') >= 0
  92. ) {
  93. data = `"${data}"`;
  94. }
  95. return data;
  96. }
  97. }
  98. export default JSONToCSV;