Merge branch 'n1073645-JSONTOCSV'

This commit is contained in:
n1474335 2021-02-01 16:35:09 +00:00
commit cf532f1e30
2 changed files with 115 additions and 30 deletions

View File

@ -6,6 +6,8 @@
import Operation from "../Operation.mjs"; import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs"; import OperationError from "../errors/OperationError.mjs";
import * as flat from "flat";
const flatten = flat.default ? flat.default.flatten : flat.flatten;
/** /**
* JSON to CSV operation * JSON to CSV operation
@ -38,6 +40,40 @@ class JSONToCSV extends Operation {
]; ];
} }
/**
* Converts a JSON to csv equivalent.
*
* @returns {string}
*/
toCsv() {
const self = this;
// If the JSON is an array of arrays, this is easy
if (this.flattened[0] instanceof Array) {
return this.flattened
.map(row => row
.map(self.escapeCellContents.bind(self))
.join(this.cellDelim)
)
.join(this.rowDelim) +
this.rowDelim;
}
// If it's an array of dictionaries...
const header = Object.keys(this.flattened[0]);
return header
.map(self.escapeCellContents.bind(self))
.join(this.cellDelim) +
this.rowDelim +
this.flattened
.map(row => header
.map(h => row[h])
.map(self.escapeCellContents.bind(self))
.join(this.cellDelim)
)
.join(this.rowDelim) +
this.rowDelim;
}
/** /**
* @param {JSON} input * @param {JSON} input
* @param {Object[]} args * @param {Object[]} args
@ -49,42 +85,25 @@ class JSONToCSV extends Operation {
// Record values so they don't have to be passed to other functions explicitly // Record values so they don't have to be passed to other functions explicitly
this.cellDelim = cellDelim; this.cellDelim = cellDelim;
this.rowDelim = rowDelim; this.rowDelim = rowDelim;
const self = this; this.flattened = input;
if (!(this.flattened instanceof Array)) {
if (!(input instanceof Array)) { this.flattened = [input];
input = [input];
} }
try { try {
// If the JSON is an array of arrays, this is easy return this.toCsv();
if (input[0] instanceof Array) { } catch (err) {
return input try {
.map(row => row this.flattened = flatten(input);
.map(self.escapeCellContents.bind(self)) if (!(this.flattened instanceof Array)) {
.join(cellDelim) this.flattened = [this.flattened];
)
.join(rowDelim) +
rowDelim;
} }
return this.toCsv();
// If it's an array of dictionaries...
const header = Object.keys(input[0]);
return header
.map(self.escapeCellContents.bind(self))
.join(cellDelim) +
rowDelim +
input
.map(row => header
.map(h => row[h])
.map(self.escapeCellContents.bind(self))
.join(cellDelim)
)
.join(rowDelim) +
rowDelim;
} catch (err) { } catch (err) {
throw new OperationError("Unable to parse JSON to CSV: " + err.toString()); throw new OperationError("Unable to parse JSON to CSV: " + err.toString());
} }
} }
}
/** /**
* Correctly escapes a cell's contents based on the cell and row delimiters. * Correctly escapes a cell's contents based on the cell and row delimiters.

View File

@ -89,5 +89,71 @@ TestRegister.addTests([
args: [",", "\\r\\n"] args: [",", "\\r\\n"]
}, },
], ],
},
{
name: "JSON to CSV: nested JSON",
input: JSON.stringify({a: 1, b: {c: 2, d: 3}}),
expectedOutput: "a,b.c,b.d\r\n1,2,3\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: nested array",
input: JSON.stringify({a: 1, b: [2, 3]}),
expectedOutput: "a,b.0,b.1\r\n1,2,3\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: nested JSON, nested array",
input: JSON.stringify({a: 1, b: {c: [2, 3], d: 4}}),
expectedOutput: "a,b.c.0,b.c.1,b.d\r\n1,2,3,4\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: nested array, nested JSON",
input: JSON.stringify({a: 1, b: [{c: 3, d: 4}]}),
expectedOutput: "a,b.0.c,b.0.d\r\n1,3,4\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: nested array, nested array",
input: JSON.stringify({a: 1, b: [[2, 3]]}),
expectedOutput: "a,b.0.0,b.0.1\r\n1,2,3\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: nested JSON, nested JSON",
input: JSON.stringify({a: 1, b: { c: { d: 2, e: 3}}}),
expectedOutput: "a,b.c.d,b.c.e\r\n1,2,3\r\n",
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
} }
]); ]);