From 4b6a177ab0aa649b97e6aaa32c71ee085bae7dd2 Mon Sep 17 00:00:00 2001 From: Petter West Date: Mon, 1 Sep 2025 14:54:36 +0300 Subject: [PATCH 1/3] allow user to rename individual field headers --- src/constants.ts | 1 + src/json2csv.ts | 4 ++-- src/types.ts | 5 +++++ src/utils.ts | 8 ++++---- test/config/testCsvFilesList.ts | 1 + test/data/csv/renamedHeaderField.csv | 3 +++ test/json2csv.ts | 11 +++++++++++ 7 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 test/data/csv/renamedHeaderField.csv diff --git a/src/constants.ts b/src/constants.ts index 735cd87..3d46080 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -40,6 +40,7 @@ export const defaultJson2CsvOptions: DefaultJson2CsvOptions = { useDateIso8601Format: false, useLocaleFormat: false, wrapBooleans: false, + fieldTitleMap: Object.create({}), }; export const defaultCsv2JsonOptions: DefaultCsv2JsonOptions = { diff --git a/src/json2csv.ts b/src/json2csv.ts index f09f930..de58afd 100755 --- a/src/json2csv.ts +++ b/src/json2csv.ts @@ -4,9 +4,9 @@ import { evaluatePath } from 'doc-path'; import { deepKeysFromList } from 'deeks'; import { excelBOM, errors } from './constants'; import * as utils from './utils'; -import type { FullJson2CsvOptions, Json2CsvParams } from './types'; +import type { DefaultJson2CsvOptions, Json2CsvParams } from './types'; -export const Json2Csv = function (options: FullJson2CsvOptions) { +export const Json2Csv = function (options: DefaultJson2CsvOptions) { const wrapDelimiterCheckRegex = new RegExp(options.delimiter.wrap, 'g'), crlfSearchRegex = /\r?\n|\r/, customValueParser = options.parseValue && typeof options.parseValue === 'function' ? options.parseValue : null, diff --git a/src/types.ts b/src/types.ts index 2646d2f..0cb0e31 100644 --- a/src/types.ts +++ b/src/types.ts @@ -157,6 +157,11 @@ export interface Json2CsvOptions extends SharedConverterOptions { */ excludeKeys?: (string | RegExp)[]; + /** + * Map keys to user provided titles. + */ + fieldTitleMap?: Record; + /** * Specify how values should be converted into CSV format. This function is provided a single field value at a time and must return a `String`. * Note: Using this option may override other options, including `useDateIso8601Format` and `useLocaleFormat`. diff --git a/src/utils.ts b/src/utils.ts index 3fee1bb..87ea153 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { evaluatePath, setPath } from 'doc-path'; import { defaultJson2CsvOptions, defaultCsv2JsonOptions } from './constants'; -import type { Json2CsvOptions, Csv2JsonOptions, FullJson2CsvOptions, FullCsv2JsonOptions } from './types'; +import type { Json2CsvOptions, Csv2JsonOptions, DefaultJson2CsvOptions, FullCsv2JsonOptions } from './types'; const dateStringRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, MAX_ARRAY_LENGTH = 100000; @@ -12,7 +12,7 @@ const dateStringRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, * If a user does not provide custom options, then we use our default * If options are provided, then we set each valid key that was passed */ -export function buildJ2COptions(opts: Json2CsvOptions): FullJson2CsvOptions { +export function buildJ2COptions(opts: Json2CsvOptions): DefaultJson2CsvOptions { return { ...defaultJson2CsvOptions, ...opts, @@ -21,7 +21,7 @@ export function buildJ2COptions(opts: Json2CsvOptions): FullJson2CsvOptions { wrap: opts?.delimiter?.wrap || defaultJson2CsvOptions.delimiter.wrap, eol: opts?.delimiter?.eol || defaultJson2CsvOptions.delimiter.eol, }, - fieldTitleMap: Object.create({}), + fieldTitleMap: opts?.fieldTitleMap || Object.create({}), }; } @@ -60,7 +60,7 @@ export function deepCopy(obj: T): T { * of a string. Given the RFC4180 requirements, that means that the value is * wrapped in value wrap delimiters (usually a quotation mark on each side). */ -export function isStringRepresentation(fieldValue: string, options: FullJson2CsvOptions | FullCsv2JsonOptions) { +export function isStringRepresentation(fieldValue: string, options: DefaultJson2CsvOptions | FullCsv2JsonOptions) { const firstChar = fieldValue[0], lastIndex = fieldValue.length - 1, lastChar = fieldValue[lastIndex]; diff --git a/test/config/testCsvFilesList.ts b/test/config/testCsvFilesList.ts index cadbc23..f14ef79 100644 --- a/test/config/testCsvFilesList.ts +++ b/test/config/testCsvFilesList.ts @@ -58,6 +58,7 @@ const csvFileConfig = [ { key: 'arrayIndexesAsKeys', file: '../data/csv/arrayIndexesAsKeys.csv' }, { key: 'keyWithEndingDot', file: '../data/csv/keyWithEndingDot.csv' }, { key: 'fieldEolAtStart', file: '../data/csv/fieldEolAtStart.csv' }, + { key: 'renamedHeaderField', file: '../data/csv/renamedHeaderField.csv' }, ]; function readCsvFile(filePath: string) { diff --git a/test/data/csv/renamedHeaderField.csv b/test/data/csv/renamedHeaderField.csv new file mode 100644 index 0000000..3a44950 --- /dev/null +++ b/test/data/csv/renamedHeaderField.csv @@ -0,0 +1,3 @@ +arrayOfStrings,renamedSubField,number,isBoolean,renamedOptionalField +"[""test1"",""test2""]",subValue,5,true,this one has it +"[""test3"",""test4""]",subValue,7,false,null \ No newline at end of file diff --git a/test/json2csv.ts b/test/json2csv.ts index 75129bd..e542156 100644 --- a/test/json2csv.ts +++ b/test/json2csv.ts @@ -794,6 +794,17 @@ export function runTests() { csv = json2csv(jsonTestData.deepNestedArrays.seven_levels_deep, options); assert.equal(csv, expectedCSV); }); + + it('should rename the header fields according to options', () => { + const csv = json2csv(jsonTestData.assortedValues, { + fieldTitleMap: { + 'optionalField': 'renamedOptionalField', + 'object.subField': 'renamedSubField', + }, + }); + + assert.equal(csv, csvTestData.renamedHeaderField); + }); }); }); From d057d2423da089f72f5cb627bc26b5a64d96ee27 Mon Sep 17 00:00:00 2001 From: Petter West Date: Mon, 1 Sep 2025 15:33:04 +0300 Subject: [PATCH 2/3] remove redundant type definition --- src/types.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/types.ts b/src/types.ts index 0cb0e31..8ab71c9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,13 +194,6 @@ export interface DefaultCsv2JsonOptions extends // Then extend the types with required fields and specific fields omitted: Omit, 'keys'>, 'headerFields'>, 'parseValue'> {} -export interface FullJson2CsvOptions extends DefaultJson2CsvOptions { - /** - * Internal field that is used to map keys to user provided titles. - */ - fieldTitleMap: Record; -} - export type FullCsv2JsonOptions = DefaultCsv2JsonOptions export interface HeaderField { From a617948ae26649196a3e9e3c06e7df701a4dba89 Mon Sep 17 00:00:00 2001 From: Petter West Date: Wed, 3 Sep 2025 15:53:20 +0300 Subject: [PATCH 3/3] add documentation for fieldTitleMap --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a1adfff..e184baf 100755 --- a/README.md +++ b/README.md @@ -129,6 +129,9 @@ Returns the CSV `string` or rejects with an `Error` if there was an issue. * `false` uses the following keys: * `['specifications']` * Note: This may result in CSV output that does not map back exactly to the original JSON. See #102 for more information. + * `fieldTitleMap` - Object - Specify field titles that should be renamed. + * Default: `{}` + * Example: `{ "key1": "Key 1", "key2": "Key 2"}` * `keys` - Array - Specify the keys that should be converted. * Default: These will be auto-detected from your data by default. * Keys can either be specified as a String representing the key path that should be converted, or as an Object of the following format: