Skip to content

Commit

Permalink
perf: bring back v5 perf improvements and add some new ones
Browse files Browse the repository at this point in the history
  • Loading branch information
juanjoDiaz committed Jun 29, 2022
1 parent 41204bc commit 288800b
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 40 deletions.
16 changes: 4 additions & 12 deletions packages/formatters/src/number.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
function toFixedDecimals(value, decimals) {
return value.toFixed(decimals);
}

function replaceSeparator(value, separator) {
return value.replace('.', separator);
}

export default function numberFormatter(opts = {}) {
if (opts.separator) {
if (opts.decimals) {
return (value) =>
replaceSeparator(toFixedDecimals(value, opts.decimals), opts.separator);
value.toFixed(opts.decimals).replace('.', opts.separator);
}

return (value) => replaceSeparator(value.toString(), opts.separator);
return (value) => `${value}`.replace('.', opts.separator);
}

if (opts.decimals) {
return (value) => toFixedDecimals(value, opts.decimals);
return (value) => value.toFixed(opts.decimals);
}

return (value) => value.toString();
return (value) => `${value}`;
}
2 changes: 1 addition & 1 deletion packages/formatters/src/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function stringFormatter(opts = {}) {
? opts.escapedQuote
: `${quote}${quote}`;

if (!quote) {
if (!quote || quote === escapedQuote) {
return (value) => value;
}

Expand Down
22 changes: 13 additions & 9 deletions packages/parsers/src/BaseParser.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import lodashGet from 'lodash.get';
import { getProp } from './utils.js';
import defaultFormatter from '@json2csv/formatters/default';
import numberFormatterCtor from '@json2csv/formatters/number';
import stringFormatterCtor from '@json2csv/formatters/string';
import symbolFormatterCtor from '@json2csv/formatters/symbol';
import objectFormatterCtor from '@json2csv/formatters/object';
import { getProp, flattenReducer, fastJoin } from './utils.js';

export default class JSON2CSVBase {
constructor(opts) {
Expand Down Expand Up @@ -118,10 +118,13 @@ export default class JSON2CSVBase {
*
* @returns {String} titles as a string
*/
getHeader(fields) {
return fields
.map((fieldInfo) => this.opts.formatters.header(fieldInfo.label))
.join(this.opts.delimiter);
getHeader() {
return fastJoin(
this.opts.fields.map((fieldInfo) =>
this.opts.formatters.header(fieldInfo.label)
),
this.opts.delimiter
);
}

/**
Expand All @@ -130,7 +133,8 @@ export default class JSON2CSVBase {
*/
preprocessRow(row) {
return this.opts.transforms.reduce(
(rows, transform) => rows.flatMap((row) => transform(row)),
(rows, transform) =>
rows.map((row) => transform(row)).reduce(flattenReducer, []),
[row]
);
}
Expand All @@ -141,12 +145,12 @@ export default class JSON2CSVBase {
* @param {Object} row JSON object to be converted in a CSV row
* @returns {String} CSV string (row)
*/
processRow(row, fields) {
processRow(row) {
if (!row) {
return undefined;
}

const processedRow = fields.map((fieldInfo) =>
const processedRow = this.opts.fields.map((fieldInfo) =>
this.processCell(row, fieldInfo)
);

Expand All @@ -157,7 +161,7 @@ export default class JSON2CSVBase {
return undefined;
}

return processedRow.join(this.opts.delimiter);
return fastJoin(processedRow, this.opts.delimiter);
}

/**
Expand Down
29 changes: 16 additions & 13 deletions packages/parsers/src/Parser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import JSON2CSVBase from './BaseParser.js';
import { flattenReducer, fastJoin } from './utils.js';

export default class JSON2CSVParser extends JSON2CSVBase {
constructor(opts) {
Expand All @@ -11,12 +12,12 @@ export default class JSON2CSVParser extends JSON2CSVBase {
* @returns {String} The CSV formated data as a string
*/
parse(data) {
const processedData = this.preprocessData(data, this.opts.fields);
const preprocessedData = this.preprocessData(data);

const fields =
this.opts.fields =
this.opts.fields ||
this.preprocessFieldsInfo(
processedData.reduce((fields, item) => {
preprocessedData.reduce((fields, item) => {
Object.keys(item).forEach((field) => {
if (!fields.includes(field)) {
fields.push(field);
Expand All @@ -27,8 +28,8 @@ export default class JSON2CSVParser extends JSON2CSVBase {
}, [])
);

const header = this.opts.header ? this.getHeader(fields) : '';
const rows = this.processData(processedData, fields);
const header = this.opts.header ? this.getHeader() : '';
const rows = this.processData(preprocessedData);
const csv =
(this.opts.withBOM ? '\ufeff' : '') +
header +
Expand All @@ -44,11 +45,11 @@ export default class JSON2CSVParser extends JSON2CSVBase {
*
* @param {Array|Object} data Array or object to be converted to CSV
*/
preprocessData(data, fields) {
preprocessData(data) {
const processedData = Array.isArray(data) ? data : [data];

if (
!fields &&
!this.opts.fields &&
(processedData.length === 0 || typeof processedData[0] !== 'object')
) {
throw new Error(
Expand All @@ -58,7 +59,9 @@ export default class JSON2CSVParser extends JSON2CSVBase {

if (this.opts.transforms.length === 0) return processedData;

return processedData.flatMap((row) => this.preprocessRow(row));
return processedData
.map((row) => this.preprocessRow(row))
.reduce(flattenReducer, []);
}

/**
Expand All @@ -67,10 +70,10 @@ export default class JSON2CSVParser extends JSON2CSVBase {
* @param {Array} data Array of JSON objects to be converted to CSV
* @returns {String} CSV string (body)
*/
processData(data, fields) {
return data
.map((row) => this.processRow(row, fields))
.filter((row) => row) // Filter empty rows
.join(this.opts.eol);
processData(data) {
return fastJoin(
data.map((row) => this.processRow(row)).filter((row) => row), // Filter empty rows
this.opts.eol
);
}
}
4 changes: 2 additions & 2 deletions packages/parsers/src/StreamParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class JSON2CSVStreamParser extends JSON2CSVBase {
}

if (this.opts.header) {
const header = this.getHeader(this.opts.fields);
const header = this.getHeader();
this.onHeader(header);
this.onData(header);
this._hasWritten = true;
Expand All @@ -147,7 +147,7 @@ export default class JSON2CSVStreamParser extends JSON2CSVBase {
}

processedData.forEach((row) => {
const line = this.processRow(row, this.opts.fields);
const line = this.processRow(row);
if (line === undefined) return;
this.onLine(line);
this.onData(this._hasWritten ? this.opts.eol + line : line);
Expand Down
30 changes: 29 additions & 1 deletion packages/parsers/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
export function getProp(obj, path, defaultValue) {
return obj[path] === undefined ? defaultValue : obj[path];
const value = obj[path];
return value === undefined ? defaultValue : value;
}

export function flattenReducer(acc, arr) {
try {
// This is faster but susceptible to `RangeError: Maximum call stack size exceeded`
acc.push(...arr);
return acc;
} catch (err) {
// Fallback to a slower but safer option
return acc.concat(arr);
}
}

export function fastJoin(arr, separator) {
let isFirst = true;
return arr.reduce((acc, elem) => {
if (elem === null || elem === undefined) {
elem = '';
}

if (isFirst) {
isFirst = false;
return `${elem}`;
}

return `${acc}${separator}${elem}`;
}, '');
}
5 changes: 3 additions & 2 deletions packages/transforms/src/unwind.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import lodashGet from 'lodash.get';
import { setProp, unsetProp } from './utils.js';
import { setProp, unsetProp, flattenReducer } from './utils.js';

function getUnwindablePaths(obj, currentPath) {
return Object.keys(obj).reduce((unwindablePaths, key) => {
Expand All @@ -20,7 +20,8 @@ function getUnwindablePaths(obj, currentPath) {
unwindablePaths.push(newPath);
unwindablePaths = unwindablePaths.concat(
value
.flatMap((arrObj) => getUnwindablePaths(arrObj, newPath))
.map((arrObj) => getUnwindablePaths(arrObj, newPath))
.reduce(flattenReducer, [])
.filter((item, index, arr) => arr.indexOf(item) !== index)
);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/transforms/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ export function unsetProp(obj, path) {
{}
);
}

export function flattenReducer(acc, arr) {
try {
// This is faster but susceptible to `RangeError: Maximum call stack size exceeded`
acc.push(...arr);
return acc;
} catch (err) {
// Fallback to a slower but safer option
return acc.concat(arr);
}
}

0 comments on commit 288800b

Please sign in to comment.