Skip to content

Commit 0b331fc

Browse files
authored
fix: Fix issue with unwind and empty arrays creating an extra column (#496)
* fix: Fix issue with unwind and empty arrays creating an extra column * fix: bring text coverage back to 100%
1 parent b37e242 commit 0b331fc

File tree

11 files changed

+173
-37
lines changed

11 files changed

+173
-37
lines changed

lib/transforms/unwind.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
const lodashGet = require('lodash.get');
3-
const { setProp, flattenReducer } = require('../utils');
3+
const { setProp, unsetProp, flattenReducer } = require('../utils');
44

55
function getUnwindablePaths(obj, currentPath) {
66
return Object.keys(obj).reduce((unwindablePaths, key) => {
@@ -42,7 +42,7 @@ function unwind({ paths = undefined, blankOut = false } = {}) {
4242
}
4343

4444
if (!unwindArray.length) {
45-
return setProp(row, unwindPath, undefined);
45+
return unsetProp(row, unwindPath);
4646
}
4747

4848
return unwindArray.map((unwindRow, index) => {

lib/utils.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,29 @@ function getProp(obj, path, defaultValue) {
66

77
function setProp(obj, path, value) {
88
const pathArray = Array.isArray(path) ? path : path.split('.');
9-
const key = pathArray[0];
10-
const newValue = pathArray.length > 1 ? setProp(obj[key] || {}, pathArray.slice(1), value) : value;
9+
const [key, ...restPath] = pathArray;
10+
const newValue = pathArray.length > 1 ? setProp(obj[key] || {}, restPath, value) : value;
1111
return Object.assign({}, obj, { [key]: newValue });
1212
}
1313

14+
function unsetProp(obj, path) {
15+
const pathArray = Array.isArray(path) ? path : path.split('.');
16+
const [key, ...restPath] = pathArray;
17+
18+
if (typeof obj[key] !== 'object') {
19+
// This will never be hit in the current code because unwind does the check before calling unsetProp
20+
/* istanbul ignore next */
21+
return obj;
22+
}
23+
24+
if (pathArray.length === 1) {
25+
delete obj[key];
26+
return obj;
27+
}
28+
29+
return unsetProp(obj[key], restPath);
30+
}
31+
1432
function flattenReducer(acc, arr) {
1533
try {
1634
// This is faster but susceptible to `RangeError: Maximum call stack size exceeded`
@@ -41,6 +59,7 @@ function fastJoin(arr, separator) {
4159
module.exports = {
4260
getProp,
4361
setProp,
62+
unsetProp,
4463
fastJoin,
4564
flattenReducer
4665
};

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/CLI.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,4 +830,16 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
830830
t.end();
831831
});
832832
});
833+
834+
testRunner.add('should unwind complex objects using the unwind transform', (t) => {
835+
const opts = '--fields carModel,price,extras.items.name,extras.items.items.position,extras.items.items.color,extras.items.items,name,color,extras.items.color'
836+
+ ' --unwind extras.items,extras.items.items --flatten-objects --flatten-arrays';
837+
838+
exec(`${cli} -i "${getFixturePath('/json/unwindComplexObject.json')}" ${opts}`, (err, stdout, stderr) => {
839+
t.notOk(stderr);
840+
const csv = stdout;
841+
t.equal(csv, csvFixtures.unwindComplexObject);
842+
t.end();
843+
});
844+
});
833845
};

test/JSON2CSVAsyncParser.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,23 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) =
12601260
t.end();
12611261
});
12621262

1263+
testRunner.add('should unwind complex objects using the unwind transform', async (t) => {
1264+
const opts = {
1265+
fields: ["carModel", "price", "extras.items.name", "extras.items.items.position", "extras.items.items.color", "extras.items.items", "name", "color", "extras.items.color"],
1266+
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
1267+
};
1268+
1269+
const parser = new AsyncParser(opts);
1270+
try {
1271+
const csv = await parser.fromInput(jsonFixtures.unwindComplexObject()).promise();
1272+
t.equal(csv, csvFixtures.unwindComplexObject);
1273+
} catch(err) {
1274+
t.fail(err.message);
1275+
}
1276+
t.end();
1277+
});
1278+
1279+
12631280
testRunner.add('should support custom transforms', async (t) => {
12641281
const opts = {
12651282
transforms: [row => ({

test/JSON2CSVParser.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,18 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
781781
t.end();
782782
});
783783

784+
testRunner.add('should unwind complex objects using the unwind transform', (t) => {
785+
const opts = {
786+
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
787+
};
788+
789+
const parser = new Json2csvParser(opts);
790+
const csv = parser.parse(jsonFixtures.unwindComplexObject);
791+
792+
t.equal(csv, csvFixtures.unwindComplexObject);
793+
t.end();
794+
});
795+
784796
testRunner.add('should support custom transforms', (t) => {
785797
const opts = {
786798
transforms: [row => ({

test/JSON2CSVTransform.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,29 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) =
12951295
});
12961296
});
12971297

1298+
1299+
testRunner.add('should unwind complex objects using the unwind transform', async (t) => {
1300+
const opts = {
1301+
fields: ["carModel", "price", "extras.items.name", "extras.items.items.position", "extras.items.items.color", "extras.items.items", "name", "color", "extras.items.color"],
1302+
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
1303+
};
1304+
1305+
const transform = new Json2csvTransform(opts);
1306+
const processor = jsonFixtures.unwindComplexObject().pipe(transform);
1307+
1308+
let csv = '';
1309+
processor
1310+
.on('data', chunk => (csv += chunk.toString()))
1311+
.on('end', () => {
1312+
t.equal(csv, csvFixtures.unwindComplexObject);
1313+
t.end();
1314+
})
1315+
.on('error', err => {
1316+
t.fail(err.message);
1317+
t.end();
1318+
});
1319+
});
1320+
12981321
testRunner.add('should support custom transforms', (t) => {
12991322
const opts = {
13001323
transforms: [row => ({

test/fixtures/csv/unwindAndFlatten.csv

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
"BMW",15000,"airbag","white"
33
"BMW",15000,"dashboard","black"
44
"Porsche",30000,"airbag","gray"
5-
"Porsche",30000,"dashboard","red"
5+
"Porsche",30000,"dashboard","red"
6+
"Mercedes",20000,,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"carModel","price","extras.items.name","extras.items.items.position","extras.items.items.color","extras.items.items","name","color","extras.items.color"
2+
"Porsche",30000,"airbag","left","white",,,,
3+
"Porsche",30000,"airbag","right","gray",,,,
4+
"Porsche",30000,"dashboard",,,"none",,,
5+
,,,,,,"airbag","white",
6+
"BMW",15000,"dashboard",,,,,,"black"
Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
[
2-
{
3-
"carModel": "BMW",
4-
"price": 15000,
5-
"items": [
6-
{
7-
"name": "airbag",
8-
"color": "white"
9-
}, {
10-
"name": "dashboard",
11-
"color": "black"
12-
}
13-
]
14-
}, {
15-
"carModel": "Porsche",
16-
"price": 30000,
17-
"items": [
18-
{
19-
"name": "airbag",
20-
"color": "gray"
21-
},
22-
{
23-
"name": "dashboard",
24-
"color": "red"
25-
}
26-
]
27-
}
2+
{
3+
"carModel": "BMW",
4+
"price": 15000,
5+
"items": [
6+
{
7+
"name": "airbag",
8+
"color": "white"
9+
}, {
10+
"name": "dashboard",
11+
"color": "black"
12+
}
13+
]
14+
}, {
15+
"carModel": "Porsche",
16+
"price": 30000,
17+
"items": [
18+
{
19+
"name": "airbag",
20+
"color": "gray"
21+
},
22+
{
23+
"name": "dashboard",
24+
"color": "red"
25+
}
26+
]
27+
},
28+
{
29+
"carModel": "Mercedes",
30+
"price": 20000,
31+
"items": []
32+
}
2833
]

0 commit comments

Comments
 (0)