Skip to content

Commit

Permalink
Merge pull request #515 from DataDog/louiszawadzki/improve-deep-clone…
Browse files Browse the repository at this point in the history
…-performance

Improve deep clone performance
  • Loading branch information
louiszawadzki authored Aug 2, 2023
2 parents 84f6dae + bf4d01f commit 1537737
Showing 1 changed file with 29 additions and 17 deletions.
46 changes: 29 additions & 17 deletions packages/core/src/sdk/EventMappers/utils/deepClone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,42 @@
* Copyright 2016-Present Datadog, Inc.
*/

const isDate = (object: unknown): object is Date => {
const type = {}.toString.call(object).slice(8, -1);
const isDate = (type: string, object: unknown): object is Date => {
return type === 'Date';
};

const isArray = (object: unknown): object is unknown[] => {
const type = {}.toString.call(object).slice(8, -1);
const isArray = (type: string, object: unknown): object is unknown[] => {
return type === 'Array';
};

const isObject = (object: unknown): object is Record<string, unknown> => {
const type = {}.toString.call(object).slice(8, -1);
const isObject = (
type: string,
object: unknown
): object is Record<string, unknown> => {
return type === 'Object';
};

const isSet = (object: unknown): object is Set<unknown> => {
const type = {}.toString.call(object).slice(8, -1);
const isSet = (type: string, object: unknown): object is Set<unknown> => {
return type === 'Set';
};

const isMap = (object: unknown): object is Map<string, unknown> => {
const type = {}.toString.call(object).slice(8, -1);
const isMap = (
type: string,
object: unknown
): object is Map<string, unknown> => {
return type === 'Map';
};

/**
* Be careful when changing this value, it can lead to performance issues: https://github.com/DataDog/dd-sdk-reactnative/issues/514.
* Benchmark of average execution time for an action event mapper with different max depth on iOS simulator:
* 7: 40ms
* 6: 15ms
* 5: 4ms
* 4: <1ms
*/
const MAX_DEPTH = 4;

/**
* Simple deep clone inspired from https://github.com/angus-c/just/blob/master/packages/collection-clone/index.cjs
* Functions and RegExp will be returned as they are.
Expand All @@ -39,21 +50,22 @@ const isMap = (object: unknown): object is Map<string, unknown> => {
* @returns
*/
export const deepClone = <T>(originalObject: T, depth: number = 0): T => {
if (isDate(originalObject)) {
const type = {}.toString.call(originalObject).slice(8, -1);
if (isDate(type, originalObject)) {
return (new Date(originalObject.getTime()) as unknown) as T;
}
if (isSet(originalObject)) {
if (isSet(type, originalObject)) {
return (new Set(
[...originalObject].map(value => deepClone(value))
) as unknown) as T;
}
if (isMap(originalObject)) {
if (isMap(type, originalObject)) {
return (new Map(
[...originalObject].map(kv => [deepClone(kv[0]), deepClone(kv[1])])
) as unknown) as T;
}
if (isArray(originalObject)) {
if (depth >= 7) {
if (isArray(type, originalObject)) {
if (depth >= MAX_DEPTH) {
// Break the circular reference here
return originalObject;
}
Expand All @@ -66,8 +78,8 @@ export const deepClone = <T>(originalObject: T, depth: number = 0): T => {

return (result as unknown) as T;
}
if (isObject(originalObject)) {
if (depth >= 7) {
if (isObject(type, originalObject)) {
if (depth >= MAX_DEPTH) {
// Break the circular reference here
return originalObject;
}
Expand Down

0 comments on commit 1537737

Please sign in to comment.