diff --git a/README.md b/README.md index bf92009..a4d11c3 100755 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Returns the CSV `string` or rejects with an `Error` if there was an issue. * `emptyFieldValue` - Any - Value that, if specified, will be substituted in for field values that are `undefined`, `null`, or an empty string. * Default: none * `excelBOM` - Boolean - Should a unicode character be prepended to allow Excel to open a UTF-8 encoded file with non-ASCII characters present. - * `excludeKeys` - Array - Specify the keys that should be excluded from the output. + * `excludeKeys` - Array - Specify the keys that should be excluded from the output. Provided keys will also be used as a RegExp to help exclude keys under a specified prefix, such as all keys of Objects in an Array when `expandArrayObjects` is `true`. * Default: `[]` * Note: When used with `unwindArrays`, arrays present at excluded key paths will not be unwound. * `expandNestedObjects` - Boolean - Should nested objects be deep-converted to CSV? diff --git a/src/json2csv.ts b/src/json2csv.ts index 96650be..a49d4f8 100755 --- a/src/json2csv.ts +++ b/src/json2csv.ts @@ -82,7 +82,15 @@ export const Json2Csv = function(options: FullJson2CsvOptions) { function filterExcludedKeys(keyPaths: string[]) { if (options.excludeKeys) { return keyPaths.filter((keyPath) => { - return !options.excludeKeys.includes(keyPath); + for (const excludedKey of options.excludeKeys) { + // Only match if the excludedKey appears at the beginning of the string so we don't accidentally match a key farther down in a key path + const regex = new RegExp(`^${excludedKey}`); + + if (excludedKey === keyPath || keyPath.match(regex)) { + return false; // Exclude the key + } + } + return true; // Otherwise, include the key }); } diff --git a/test/config/testCsvFilesList.ts b/test/config/testCsvFilesList.ts index 634d459..d1cd61f 100644 --- a/test/config/testCsvFilesList.ts +++ b/test/config/testCsvFilesList.ts @@ -50,6 +50,7 @@ const csvFileConfig = [ {key: 'falsyValues', file: '../data/csv/falsyValues.csv'}, {key: 'nestedNotUnwoundObjects', file: '../data/csv/nestedNotUnwoundObjects.csv'}, {key: 'newlineWithWrapDelimiters', file: '../data/csv/newlineWithWrapDelimiters.csv'}, + {key: 'excludeKeyPattern', file: '../data/csv/excludeKeyPattern.csv'}, ]; function readCsvFile(filePath: string) { diff --git a/test/config/testJsonFilesList.ts b/test/config/testJsonFilesList.ts index 6c53836..e617909 100644 --- a/test/config/testJsonFilesList.ts +++ b/test/config/testJsonFilesList.ts @@ -43,4 +43,5 @@ export default { quotedFieldWithNewline: require('../data/json/quotedFieldWithNewline.json'), falsyValues: require('../data/json/falsyValues.json'), newlineWithWrapDelimiters: require('../data/json/newlineWithWrapDelimiters'), + excludeKeyPattern: require('../data/json/excludeKeyPattern'), }; diff --git a/test/data/csv/excludeKeyPattern.csv b/test/data/csv/excludeKeyPattern.csv new file mode 100644 index 0000000..a205f3c --- /dev/null +++ b/test/data/csv/excludeKeyPattern.csv @@ -0,0 +1,3 @@ +id,name.arr +1,this should appear +2,this should also appear \ No newline at end of file diff --git a/test/data/json/excludeKeyPattern.json b/test/data/json/excludeKeyPattern.json new file mode 100644 index 0000000..ca25f11 --- /dev/null +++ b/test/data/json/excludeKeyPattern.json @@ -0,0 +1,24 @@ +[ + { + "id": 1, + "arr": [ + { + "name": "foo" + } + ], + "name": { + "arr": "this should appear" + } + }, + { + "id": 2, + "arr": [ + { + "name": "bar" + } + ], + "name": { + "arr": "this should also appear" + } + } +] \ No newline at end of file diff --git a/test/json2csv.ts b/test/json2csv.ts index 82f1265..27a4748 100644 --- a/test/json2csv.ts +++ b/test/json2csv.ts @@ -478,6 +478,51 @@ export function runTests() { assert.equal(csv, updatedCsv); }); + // Test case for #244 + it('should exclude a matched key prefix from the output when unwinding arrays', () => { + const updatedCsv = csvTestData.unwind.replace(',data.options.name', '') + .replace(/,MacBook (Pro|Air) \d+/g, '') + .replace(/,(Super|Turbo)charger/g, '') + .replace('5cf7ca3616c91100018844af,Computers\n', '') + // Remove duplicate lines + .replace('5cf7ca3616c91100018844bf,Cars\n', ''); + + const csv = json2csv(jsonTestData.unwind, { + unwindArrays: true, + expandArrayObjects: true, + excludeKeys: ['data.options'] + }); + + assert.equal(csv, updatedCsv); + }); + + // Test case for #244 + it('should exclude a matched key prefix from the output when unwinding arrays', () => { + const updatedCsv = csvTestData.unwind.replace(',data.category,data.options.name', '') + .replace(/,Computers,MacBook (Pro|Air) \d+/g, '') + .replace(/,Cars,(Super|Turbo)charger/g, '') + .replace('5cf7ca3616c91100018844af\n', '') + // Remove duplicate lines + .replace('5cf7ca3616c91100018844bf\n', ''); + + const csv = json2csv(jsonTestData.unwind, { + unwindArrays: true, + excludeKeys: ['data'] + }); + + assert.equal(csv, updatedCsv); + }); + + // Test case for #244 + it('should exclude a matched key prefix, but not if it is not at the start of the key path', () => { + const csv = json2csv(jsonTestData.excludeKeyPattern, { + expandArrayObjects: true, + excludeKeys: ['arr'] + }); + + assert.equal(csv, csvTestData.excludeKeyPattern); + }); + it('should use a custom value parser function when provided', () => { const updatedCsv = csvTestData.trimmedFields.split('\n'); const textRow = 'Parsed Value,Parsed Value,Parsed Value,Parsed Value,Parsed Value';