Skip to content

Commit

Permalink
fix: issue with unwind and empty arrays creating an extra column (#497)
Browse files Browse the repository at this point in the history
* fix: Fix issue with unwind and empty arrays creating an extra column

* fix: bring text coverage back to 100%

* fix: don't mutate original object in unset
  • Loading branch information
juanjoDiaz authored Nov 11, 2020
1 parent 4d8a81a commit 3b74735
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 38 deletions.
4 changes: 2 additions & 2 deletions lib/transforms/unwind.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

const lodashGet = require('lodash.get');
const { setProp } = require('../utils');
const { setProp, unsetProp } = require('../utils');

function getUnwindablePaths(obj, currentPath) {
return Object.keys(obj).reduce((unwindablePaths, key) => {
Expand Down Expand Up @@ -41,7 +41,7 @@ function unwind({ paths = undefined, blankOut = false } = {}) {
}

if (!unwindArray.length) {
return setProp(row, unwindPath, undefined);
return unsetProp(row, unwindPath);
}

return unwindArray.map((unwindRow, index) => {
Expand Down
26 changes: 23 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,32 @@ function getProp(obj, path, defaultValue) {

function setProp(obj, path, value) {
const pathArray = Array.isArray(path) ? path : path.split('.');
const key = pathArray[0];
const newValue = pathArray.length > 1 ? setProp(obj[key] || {}, pathArray.slice(1), value) : value;
return Object.assign({}, obj, { [key]: newValue });
const [key, ...restPath] = pathArray;
const newValue = pathArray.length > 1 ? setProp(obj[key] || {}, restPath, value) : value;
return { ...obj, [key]: newValue };
}

function unsetProp(obj, path) {
const pathArray = Array.isArray(path) ? path : path.split('.');
const [key, ...restPath] = pathArray;

if (typeof obj[key] !== 'object') {
// This will never be hit in the current code because unwind does the check before calling unsetProp
/* istanbul ignore next */
return obj;
}

if (pathArray.length === 1) {
return Object.keys(obj)
.filter(prop => prop !== key)
.reduce((acc, prop) => ({ ...acc, [prop]: obj[prop] }), {});
}

return unsetProp(obj[key], restPath);
}

module.exports = {
getProp,
setProp,
unsetProp,
};
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions test/CLI.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,19 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
});
});


testRunner.add('should unwind complex objects using the unwind transform', (t) => {
const opts = '--fields carModel,price,extras.items.name,extras.items.items.position,extras.items.items.color,extras.items.items,name,color,extras.items.color'
+ ' --unwind extras.items,extras.items.items --flatten-objects --flatten-arrays';

exec(`${cli} -i "${getFixturePath('/json/unwindComplexObject.json')}" ${opts}`, (err, stdout, stderr) => {
t.notOk(stderr);
const csv = stdout;
t.equal(csv, csvFixtures.unwindComplexObject);
t.end();
});
});

// Formatters


Expand Down
16 changes: 16 additions & 0 deletions test/JSON2CSVAsyncParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,22 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) =
t.end();
});

testRunner.add('should unwind complex objects using the unwind transform', async (t) => {
const opts = {
fields: ["carModel", "price", "extras.items.name", "extras.items.items.position", "extras.items.items.color", "extras.items.items", "name", "color", "extras.items.color"],
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
};

const parser = new AsyncParser(opts);
try {
const csv = await parser.parse(jsonFixtures.unwindComplexObject()).promise();
t.equal(csv, csvFixtures.unwindComplexObject);
} catch(err) {
t.fail(err.message);
}
t.end();
});

testRunner.add('should support custom transforms', async (t) => {
const opts = {
transforms: [row => ({
Expand Down
12 changes: 12 additions & 0 deletions test/JSON2CSVParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,18 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
t.end();
});

testRunner.add('should unwind complex objects using the unwind transform', (t) => {
const opts = {
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
};

const parser = new Json2csvParser(opts);
const csv = parser.parse(jsonFixtures.unwindComplexObject);

t.equal(csv, csvFixtures.unwindComplexObject);
t.end();
});

testRunner.add('should support custom transforms', (t) => {
const opts = {
transforms: [row => ({
Expand Down
23 changes: 23 additions & 0 deletions test/JSON2CSVTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,29 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) =
});
});


testRunner.add('should unwind complex objects using the unwind transform', async (t) => {
const opts = {
fields: ["carModel", "price", "extras.items.name", "extras.items.items.position", "extras.items.items.color", "extras.items.items", "name", "color", "extras.items.color"],
transforms: [unwind({ paths: ['extras.items', 'extras.items.items'] }), flatten()],
};

const transform = new Json2csvTransform(opts);
const processor = jsonFixtures.unwindComplexObject().pipe(transform);

let csv = '';
processor
.on('data', chunk => (csv += chunk.toString()))
.on('end', () => {
t.equal(csv, csvFixtures.unwindComplexObject);
t.end();
})
.on('error', err => {
t.fail(err.message);
t.end();
});
});

testRunner.add('should support custom transforms', (t) => {
const opts = {
transforms: [row => ({
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/csv/unwindAndFlatten.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
"BMW",15000,"airbag","white"
"BMW",15000,"dashboard","black"
"Porsche",30000,"airbag","gray"
"Porsche",30000,"dashboard","red"
"Porsche",30000,"dashboard","red"
"Mercedes",20000,,
6 changes: 6 additions & 0 deletions test/fixtures/csv/unwindComplexObject.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"carModel","price","extras.items.name","extras.items.items.position","extras.items.items.color","extras.items.items","name","color","extras.items.color"
"Porsche",30000,"airbag","left","white",,,,
"Porsche",30000,"airbag","right","gray",,,,
"Porsche",30000,"dashboard",,,"none",,,
,,,,,,"airbag","white",
"BMW",15000,"dashboard",,,,,,"black"
57 changes: 31 additions & 26 deletions test/fixtures/json/unwindAndFlatten.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
[
{
"carModel": "BMW",
"price": 15000,
"items": [
{
"name": "airbag",
"color": "white"
}, {
"name": "dashboard",
"color": "black"
}
]
}, {
"carModel": "Porsche",
"price": 30000,
"items": [
{
"name": "airbag",
"color": "gray"
},
{
"name": "dashboard",
"color": "red"
}
]
}
{
"carModel": "BMW",
"price": 15000,
"items": [
{
"name": "airbag",
"color": "white"
}, {
"name": "dashboard",
"color": "black"
}
]
}, {
"carModel": "Porsche",
"price": 30000,
"items": [
{
"name": "airbag",
"color": "gray"
},
{
"name": "dashboard",
"color": "red"
}
]
},
{
"carModel": "Mercedes",
"price": 20000,
"items": []
}
]
41 changes: 41 additions & 0 deletions test/fixtures/json/unwindComplexObject.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[
{
"carModel": "Porsche",
"price": 30000,
"extras": {
"items": [
{
"name": "airbag",
"items": [
{
"position": "left",
"color": "white"
}, {
"position": "right",
"color": "gray"
}
]
},
{
"name": "dashboard",
"items": "none"
}
]
}
}, {
"carModel": "BMW",
"price": 15000,
"extras": {
"items": [
{
"name": "airbag",
"color": "white",
"items": []
}, {
"name": "dashboard",
"color": "black"
}
]
}
}
]

0 comments on commit 3b74735

Please sign in to comment.