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

View file

@ -89,5 +89,71 @@ TestRegister.addTests([
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"]
},
],
}
]);