Skip to content

Commit 2c3b86a

Browse files
committed
[#34] Added key delimiter
1 parent 3cde3fa commit 2c3b86a

File tree

5 files changed

+41
-16
lines changed

5 files changed

+41
-16
lines changed

src/lib/csv-stringifier-factory.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ObjectCsvStringifierParams {
1414
header: ObjectStringifierHeader;
1515
fieldDelimiter?: string;
1616
recordDelimiter?: string;
17+
keyDelimiter?: string;
1718
alwaysQuote?: boolean;
1819
}
1920

@@ -26,7 +27,7 @@ export class CsvStringifierFactory {
2627

2728
createObjectCsvStringifier(params: ObjectCsvStringifierParams) {
2829
const fieldStringifier = createFieldStringifier(params.fieldDelimiter, params.alwaysQuote);
29-
return new ObjectCsvStringifier(fieldStringifier, params.header, params.recordDelimiter);
30+
return new ObjectCsvStringifier(fieldStringifier, params.header, params.recordDelimiter, params.keyDelimiter);
3031
}
3132

3233
}

src/lib/csv-stringifiers/abstract.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,27 @@ export abstract class CsvStringifier<T> {
1818

1919
getHeaderString(): string | null {
2020
const headerRecord = this.getHeaderRecord();
21-
return headerRecord ? this.stringifyRecords([headerRecord]) : null;
21+
return headerRecord ? this.joinRecords([this.getCsvLine(headerRecord)]) : null;
2222
}
2323

2424
stringifyRecords(records: IterableIterator<T> | T[]): string {
2525
const csvLines = Array.from(records, record => this.getCsvLine(this.getRecordAsArray(record)));
26-
return csvLines.join(this.recordDelimiter) + this.recordDelimiter;
26+
return this.joinRecords(csvLines);
2727
}
2828

2929
protected abstract getRecordAsArray(_record: T): Field[];
3030

31-
protected abstract getHeaderRecord(): T | null | undefined;
31+
protected abstract getHeaderRecord(): string[] | null | undefined;
3232

3333
private getCsvLine(record: Field[]): string {
3434
return record
3535
.map(fieldValue => this.fieldStringifier.stringify(fieldValue))
3636
.join(this.fieldDelimiter);
3737
}
38+
39+
private joinRecords(records: string[]) {
40+
return records.join(this.recordDelimiter) + this.recordDelimiter;
41+
}
3842
}
3943

4044
function _validateRecordDelimiter(delimiter: string): void {

src/lib/csv-stringifiers/object.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@ import {Field, ObjectHeaderItem, ObjectStringifierHeader} from '../record';
44
import {isObject, ObjectMap} from '../lang/object';
55

66
export class ObjectCsvStringifier extends CsvStringifier<ObjectMap<Field>> {
7-
private readonly header: ObjectStringifierHeader;
87

9-
constructor(fieldStringifier: FieldStringifier, header: ObjectStringifierHeader, recordDelimiter?: string) {
8+
constructor(fieldStringifier: FieldStringifier,
9+
private readonly header: ObjectStringifierHeader,
10+
recordDelimiter?: string,
11+
private readonly keyDelimiter?: string) {
1012
super(fieldStringifier, recordDelimiter);
11-
this.header = header;
1213
}
1314

14-
protected getHeaderRecord(): ObjectMap<Field> | null {
15+
protected getHeaderRecord(): string[] | null {
1516
if (!this.isObjectHeader) return null;
16-
17-
return (this.header as ObjectHeaderItem[]).reduce((memo, field) =>
18-
Object.assign({}, memo, {[field.id]: field.title}), {});
17+
return (this.header as ObjectHeaderItem[]).map(field => field.title);
1918
}
2019

2120
protected getRecordAsArray(record: ObjectMap<Field>): Field[] {
22-
return this.fieldIds.map(field => record[field]);
21+
return this.fieldIds.map(fieldId => this.getNestedValue(record, fieldId));
22+
}
23+
24+
private getNestedValue(obj: ObjectMap<Field>, key: string) {
25+
if (!this.keyDelimiter) return obj[key];
26+
return key.split(this.keyDelimiter).reduce((subObj, keyPart) => subObj[keyPart], obj);
2327
}
2428

2529
private get fieldIds(): string[] {

src/lib/csv-writer-factory.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface ObjectCsvWriterParams {
1717
header: ObjectStringifierHeader;
1818
fieldDelimiter?: string;
1919
recordDelimiter?: string;
20+
keyDelimiter?: string;
2021
alwaysQuote?: boolean;
2122
encoding?: string;
2223
append?: boolean;
@@ -44,6 +45,7 @@ export class CsvWriterFactory {
4445
header: params.header,
4546
fieldDelimiter: params.fieldDelimiter,
4647
recordDelimiter: params.recordDelimiter,
48+
keyDelimiter: params.keyDelimiter,
4749
alwaysQuote: params.alwaysQuote
4850
});
4951
return new CsvWriter(csvStringifier, params.path, params.encoding, params.append);

src/test/write-object-records.test.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ describe('Write object records into CSV', () => {
88

99
const makeFilePath = (id: string) => testFilePath(`object-${id}`);
1010
const records = [
11-
{name: 'Bob', lang: 'French'},
12-
{name: 'Mary', lang: 'English'}
11+
{name: 'Bob', lang: 'French', address: {country: 'France'}},
12+
{name: 'Mary', lang: 'English', address: {country: 'Australia'}}
1313
];
1414

1515
describe('When only path and header ids are given', () => {
1616
const filePath = makeFilePath('minimum');
17-
let writer: CsvWriter<ObjectMap<string>>;
17+
let writer: CsvWriter<ObjectMap<string|ObjectMap<string>>>;
1818

1919
beforeEach(() => {
2020
writer = createObjectCsvWriter({
@@ -50,7 +50,7 @@ describe('Write object records into CSV', () => {
5050

5151
describe('When field header is given with titles', () => {
5252
const filePath = makeFilePath('header');
53-
let writer: CsvWriter<ObjectMap<string>>;
53+
let writer: CsvWriter<ObjectMap<string|ObjectMap<string>>>;
5454

5555
beforeEach(() => {
5656
writer = createObjectCsvWriter({
@@ -141,4 +141,18 @@ describe('Write object records into CSV', () => {
141141
assertFile(filePath, '"NAME","LANGUAGE"\n"Bob","French"\n"Mary","English"\n');
142142
});
143143
});
144+
145+
describe('When `keyDelimiter` flag is set', () => {
146+
const filePath = makeFilePath('nested');
147+
const writer = createObjectCsvWriter({
148+
path: filePath,
149+
header: [{id: 'name', title: 'NAME'}, {id: 'address.country', title: 'COUNTRY'}],
150+
keyDelimiter: '.'
151+
});
152+
153+
it('breaks keys into key paths', async () => {
154+
await writer.writeRecords(records);
155+
assertFile(filePath, 'NAME,COUNTRY\nBob,France\nMary,Australia\n');
156+
});
157+
});
144158
});

0 commit comments

Comments
 (0)