mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-21 23:36:16 -04:00
'JSON to CSV' operation now escapes characters correctly. Added tests for CSV/JSON operations.
This commit is contained in:
parent
863bdffa84
commit
3a979b6cda
3 changed files with 232 additions and 12 deletions
|
@ -20,7 +20,7 @@ class JSONToCSV extends Operation {
|
||||||
|
|
||||||
this.name = "JSON to CSV";
|
this.name = "JSON to CSV";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "Converts JSON data to a CSV.";
|
this.description = "Converts JSON data to a CSV based on the definition in RFC 4180.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
|
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
|
||||||
this.inputType = "JSON";
|
this.inputType = "JSON";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
@ -46,27 +46,67 @@ class JSONToCSV extends Operation {
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [cellDelim, rowDelim] = args;
|
const [cellDelim, rowDelim] = args;
|
||||||
|
|
||||||
|
this.cellDelim = cellDelim;
|
||||||
|
this.rowDelim = rowDelim;
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
// TODO: Escape cells correctly.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If the JSON is an array of arrays, this is easy
|
// If the JSON is an array of arrays, this is easy
|
||||||
if (input[0] instanceof Array) {
|
if (input[0] instanceof Array) {
|
||||||
return input.map(row => row.join(cellDelim)).join(rowDelim) + rowDelim;
|
return input
|
||||||
|
.map(row => row
|
||||||
|
.map(self.escapeCellContents.bind(self))
|
||||||
|
.join(cellDelim)
|
||||||
|
)
|
||||||
|
.join(rowDelim) +
|
||||||
|
rowDelim;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's an array of dictionaries...
|
// If it's an array of dictionaries...
|
||||||
const header = Object.keys(input[0]);
|
const header = Object.keys(input[0]);
|
||||||
return header.join(cellDelim) +
|
return header
|
||||||
|
.map(self.escapeCellContents.bind(self))
|
||||||
|
.join(cellDelim) +
|
||||||
rowDelim +
|
rowDelim +
|
||||||
input.map(
|
input
|
||||||
row => header.map(
|
.map(row => header
|
||||||
h => row[h]
|
.map(h => row[h])
|
||||||
).join(cellDelim)
|
.map(self.escapeCellContents.bind(self))
|
||||||
).join(rowDelim) +
|
.join(cellDelim)
|
||||||
|
)
|
||||||
|
.join(rowDelim) +
|
||||||
rowDelim;
|
rowDelim;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError("Unable to parse JSON to CSV: " + 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.
|
||||||
|
*
|
||||||
|
* @param {string} data
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
escapeCellContents(data) {
|
||||||
|
// Double quotes should be doubled up
|
||||||
|
data = data.replace(/"/g, '""');
|
||||||
|
|
||||||
|
// If the cell contains a cell or row delimiter or a double quote, it mut be enclosed in double quotes
|
||||||
|
if (
|
||||||
|
data.indexOf(this.cellDelim) >= 0 ||
|
||||||
|
data.indexOf(this.rowDelim) >= 0 ||
|
||||||
|
data.indexOf("\n") >= 0 ||
|
||||||
|
data.indexOf("\r") >= 0 ||
|
||||||
|
data.indexOf('"') >= 0
|
||||||
|
) {
|
||||||
|
data = `"${data}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JSONToCSV;
|
export default JSONToCSV;
|
||||||
|
|
|
@ -39,6 +39,7 @@ import "./tests/operations/Comment";
|
||||||
import "./tests/operations/Compress";
|
import "./tests/operations/Compress";
|
||||||
import "./tests/operations/ConditionalJump";
|
import "./tests/operations/ConditionalJump";
|
||||||
import "./tests/operations/Crypt";
|
import "./tests/operations/Crypt";
|
||||||
|
import "./tests/operations/CSV";
|
||||||
import "./tests/operations/DateTime";
|
import "./tests/operations/DateTime";
|
||||||
import "./tests/operations/ExtractEmailAddresses";
|
import "./tests/operations/ExtractEmailAddresses";
|
||||||
import "./tests/operations/Fork";
|
import "./tests/operations/Fork";
|
||||||
|
@ -126,12 +127,12 @@ function handleTestResult(testResult) {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fail if the process takes longer than 10 seconds.
|
* Fail if the process takes longer than 60 seconds.
|
||||||
*/
|
*/
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
console.log("Tests took longer than 10 seconds to run, returning.");
|
console.log("Tests took longer than 60 seconds to run, returning.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}, 10 * 1000);
|
}, 60 * 1000);
|
||||||
|
|
||||||
|
|
||||||
TestRegister.runTests()
|
TestRegister.runTests()
|
||||||
|
|
179
test/tests/operations/CSV.mjs
Normal file
179
test/tests/operations/CSV.mjs
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
/**
|
||||||
|
* CSV tests.
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2018
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../TestRegister";
|
||||||
|
|
||||||
|
const EXAMPLE_CSV = `A,B,C,D,E,F\r
|
||||||
|
1,2,3,4,5,6\r
|
||||||
|
",",;,',"""",,\r
|
||||||
|
"""hello""","a""1","multi\r
|
||||||
|
line",,,end\r
|
||||||
|
`;
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "CSV to JSON: Array of dictionaries",
|
||||||
|
input: EXAMPLE_CSV,
|
||||||
|
expectedOutput: JSON.stringify([
|
||||||
|
{
|
||||||
|
"A": "1",
|
||||||
|
"B": "2",
|
||||||
|
"C": "3",
|
||||||
|
"D": "4",
|
||||||
|
"E": "5",
|
||||||
|
"F": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"A": ",",
|
||||||
|
"B": ";",
|
||||||
|
"C": "'",
|
||||||
|
"D": "\"",
|
||||||
|
"E": "",
|
||||||
|
"F": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"A": "\"hello\"",
|
||||||
|
"B": "a\"1",
|
||||||
|
"C": "multi\r\nline",
|
||||||
|
"D": "",
|
||||||
|
"E": "",
|
||||||
|
"F": "end"
|
||||||
|
}
|
||||||
|
], null, 4),
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "CSV to JSON",
|
||||||
|
args: [",", "\r\n", "Array of dictionaries"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CSV to JSON: Array of arrays",
|
||||||
|
input: EXAMPLE_CSV,
|
||||||
|
expectedOutput: JSON.stringify([
|
||||||
|
[
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
",",
|
||||||
|
";",
|
||||||
|
"'",
|
||||||
|
"\"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"\"hello\"",
|
||||||
|
"a\"1",
|
||||||
|
"multi\r\nline",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"end"
|
||||||
|
]
|
||||||
|
], null, 4),
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "CSV to JSON",
|
||||||
|
args: [",", "\r\n", "Array of arrays"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON to CSV: Array of dictionaries",
|
||||||
|
input: JSON.stringify([
|
||||||
|
{
|
||||||
|
"A": "1",
|
||||||
|
"B": "2",
|
||||||
|
"C": "3",
|
||||||
|
"D": "4",
|
||||||
|
"E": "5",
|
||||||
|
"F": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"A": ",",
|
||||||
|
"B": ";",
|
||||||
|
"C": "'",
|
||||||
|
"D": "\"",
|
||||||
|
"E": "",
|
||||||
|
"F": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"A": "\"hello\"",
|
||||||
|
"B": "a\"1",
|
||||||
|
"C": "multi\r\nline",
|
||||||
|
"D": "",
|
||||||
|
"E": "",
|
||||||
|
"F": "end"
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
expectedOutput: EXAMPLE_CSV,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "JSON to CSV",
|
||||||
|
args: [",", "\r\n"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON to CSV: Array of arrays",
|
||||||
|
input: JSON.stringify([
|
||||||
|
[
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
",",
|
||||||
|
";",
|
||||||
|
"'",
|
||||||
|
"\"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"\"hello\"",
|
||||||
|
"a\"1",
|
||||||
|
"multi\r\nline",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"end"
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
expectedOutput: EXAMPLE_CSV,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "JSON to CSV",
|
||||||
|
args: [",", "\r\n"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
Loading…
Add table
Add a link
Reference in a new issue