diff --git a/src/json2csv.ts b/src/json2csv.ts index d26fb1f..f09f930 100755 --- a/src/json2csv.ts +++ b/src/json2csv.ts @@ -239,6 +239,20 @@ export const Json2Csv = function (options: FullJson2CsvOptions) { /** RECORD FIELD FUNCTIONS **/ + function stillNeedsUnwind(params: Json2CsvParams): boolean{ + for (const record of params.records) { + for (const field of params.headerFields) { + const value = evaluatePath(record, field); + + if (Array.isArray(value)) { + return true; + } + } + } + + return false; + } + /** * Unwinds objects in arrays within record objects if the user specifies the * expandArrayObjects option. If not specified, this passes the params @@ -249,21 +263,17 @@ export const Json2Csv = function (options: FullJson2CsvOptions) { */ function unwindRecordsIfNecessary(params: Json2CsvParams, finalPass = false): Json2CsvParams { if (options.unwindArrays) { - const originalRecordsLength = params.records.length; - - // Unwind each of the documents at the given headerField + // Unwind each document at each header field params.headerFields.forEach((headerField) => { params.records = utils.unwind(params.records, headerField); }); - const headerFields = retrieveHeaderFields(params.records); - params.headerFields = headerFields; + params.headerFields = retrieveHeaderFields(params.records); - // If we were able to unwind more arrays, then try unwinding again... - if (originalRecordsLength !== params.records.length) { - return unwindRecordsIfNecessary(params); + // Continue unwinding if any nested arrays remain + if (stillNeedsUnwind(params)) { + return unwindRecordsIfNecessary(params, finalPass); } - // Otherwise, we didn't unwind any additional arrays, so continue... // Run a final time in case the earlier unwinding exposed additional // arrays to unwind... diff --git a/test/config/testJsonFilesList.ts b/test/config/testJsonFilesList.ts index 0f3c712..4815eb0 100644 --- a/test/config/testJsonFilesList.ts +++ b/test/config/testJsonFilesList.ts @@ -50,4 +50,5 @@ export default { arrayIndexesAsKeys: require('../data/json/arrayIndexesAsKeys.json'), keyWithEndingDot: require('../data/json/keyWithEndingDot.json'), fieldEolAtStart: require('../data/json/fieldEolAtStart.json'), + deepNestedArrays: require('../data/json/deepNestedArrays.json'), }; diff --git a/test/data/json/deepNestedArrays.json b/test/data/json/deepNestedArrays.json new file mode 100644 index 0000000..334a2d8 --- /dev/null +++ b/test/data/json/deepNestedArrays.json @@ -0,0 +1,116 @@ +{ + "was_already_working": [ + { + "Countries": [ + { + "Cities": [ + { + "Streets": [ + { + "Name": "Road 1", + "Number": 1 + }, + { + "Name": "Road 2", + "Number": 2 + } + ] + }, + { + "field": "value" + } + ] + } + ] + } + ], + "was_not_working_1": [ + { + "Countries": [ + { + "Cities": [ + { + "Streets": [ + { + "Name": "Road 1", + "Number": 1 + }, + { + "Name": "Road 2", + "Number": 2 + } + ] + } + ] + } + ] + } + ], + "was_not_working_2": [ + { + "Continents": [ + { + "Countries": [ + { + "Cities": [ + { + "Streets": [ + { + "Name": "Road 1", + "Number": 1 + }, + { + "Name": "Road 2", + "Number": 2 + } + ] + }, + { + "field": "value" + } + ] + } + ] + } + ] + } + ], + "seven_levels_deep": [ + { + "SolarSystems": [ + { + "Planets": [ + { + "Hemispheres": [ + { + "Continents": [ + { + "Countries": [ + { + "Cities": [ + { + "Streets": [ + { + "Name": "Road 1", + "Number": 1 + }, + { + "Name": "Road 2", + "Number": 2 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/json2csv.ts b/test/json2csv.ts index d05aab3..75129bd 100644 --- a/test/json2csv.ts +++ b/test/json2csv.ts @@ -760,6 +760,40 @@ export function runTests() { assert.equal(csv, csvTestData.nestedNotUnwoundObjects); }); + + it('should unwind all found arrays', () => { + const options = { + expandArrayObjects: true, + unwindArrays: true, + emptyFieldValue: '---' + }; + + let expectedCSV = 'Countries.Cities.Streets.Name,Countries.Cities.Streets.Number,Countries.Cities.field\n' + + 'Road 1,1,---\n' + + 'Road 2,2,---\n' + + '---,---,value'; + let csv = json2csv(jsonTestData.deepNestedArrays.was_already_working, options); + assert.equal(csv, expectedCSV); + + expectedCSV = 'Countries.Cities.Streets.Name,Countries.Cities.Streets.Number\n' + + 'Road 1,1\n' + + 'Road 2,2'; + csv = json2csv(jsonTestData.deepNestedArrays.was_not_working_1, options); + assert.equal(csv, expectedCSV); + + expectedCSV = 'Continents.Countries.Cities.Streets.Name,Continents.Countries.Cities.Streets.Number,Continents.Countries.Cities.field\n' + + 'Road 1,1,---\n' + + 'Road 2,2,---\n' + + '---,---,value'; + csv = json2csv(jsonTestData.deepNestedArrays.was_not_working_2, options); + assert.equal(csv, expectedCSV); + + expectedCSV = 'SolarSystems.Planets.Hemispheres.Continents.Countries.Cities.Streets.Name,SolarSystems.Planets.Hemispheres.Continents.Countries.Cities.Streets.Number\n' + + 'Road 1,1\n' + + 'Road 2,2'; + csv = json2csv(jsonTestData.deepNestedArrays.seven_levels_deep, options); + assert.equal(csv, expectedCSV); + }); }); });