Skip to content

Instantly share code, notes, and snippets.

@erikvullings
Last active September 3, 2024 09:02
Show Gist options
  • Save erikvullings/ada7af09925082cbb89f40ed962d475e to your computer and use it in GitHub Desktop.
Save erikvullings/ada7af09925082cbb89f40ed962d475e to your computer and use it in GitHub Desktop.
Deep copy or clone in TypeScript
/**
* Deep copy function for TypeScript.
* @param T Generic type of target/copied value.
* @param target Target value to be copied.
* @see Source project, ts-deepcopy https://github.com/ykdr2017/ts-deepcopy
* @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
*/
export const deepCopy = <T>(target: T): T => {
if (target === null) {
return target;
}
if (target instanceof Date) {
return new Date(target.getTime()) as any;
}
if (target instanceof Array) {
const cp = [] as any[];
(target as any[]).forEach((v) => { cp.push(v); });
return cp.map((n: any) => deepCopy<any>(n)) as any;
}
if (typeof target === 'object' && target !== {}) {
const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any };
Object.keys(cp).forEach(k => {
cp[k] = deepCopy<any>(cp[k]);
});
return cp as T;
}
return target;
};
@erikvullings
Copy link
Author

this looks awesome. Do you have an npm package for it out there? Thanks.

Thanks, you can get it from npm too npm i deep-copy-ts at npm.
Source.

@MohammadFakhreddin
Copy link

MohammadFakhreddin commented Dec 26, 2019

Hi there. Thanks for your code . I fixed a small issue inside it and I attached my final code. The part that you check that object is an instance of Array, there are some cases that we use libraries like RealmDb that they have defined their own array type and we need to clone that object to modify it. I changed that part to check for iterator instead and prevent it to be cloned as Object. Thanks again for your code.

export class ObjectHelper {
  /**
   * Deep copy function for TypeScript.
   * @param T Generic type of target/copied value.
   * @param target Target value to be copied.
   * @see Source project, ts-deeply https://github.com/ykdr2017/ts-deepcopy
   * @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
   */
  public static deepCopy<T>(target: T): T {
    if (target === null) {
      return target
    }
    if (target instanceof Date) {
      return new Date(target.getTime()) as any
    }
    // First part is for array and second part is for Realm.Collection
    // if (target instanceof Array || typeof (target as any).type === 'string') {
    if (typeof target === 'object') {
      if (typeof target[(Symbol as any).iterator] === 'function') {
        const cp = [] as any[]
        if ((target as any as any[]).length > 0) {
          for (const arrayMember of target as any as any[]) {
            cp.push(ObjectHelper.deepCopy(arrayMember))
          }
        }
        return cp as any as T
      } else {
        const targetKeys = Object.keys(target)
        const cp = {}
        if (targetKeys.length > 0) {
          for (const key of targetKeys) {
            cp[key] = ObjectHelper.deepCopy(target[key])
          }
        }
        return cp as T
      }
    }
    // Means that object is atomic
    return target
  }
}

@erikvullings
Copy link
Author

@MohammadFakhreddin Thanks for sharing your updates!

@alexislargaiolli
Copy link

Thanks you for this :)

@berkbltc
Copy link

Thanks a lot @erikvullings

@lebronjs
Copy link

Hi there. Thanks for your code . I fixed a small issue inside it and I attached my final code. The part that you check that object is an instance of Array, there are some cases that we use libraries like RealmDb that they have defined their own array type and we need to clone that object to modify it. I changed that part to check for iterator instead and prevent it to be cloned as Object. Thanks again for your code.

export class ObjectHelper {
  /**
   * Deep copy function for TypeScript.
   * @param T Generic type of target/copied value.
   * @param target Target value to be copied.
   * @see Source project, ts-deeply https://github.com/ykdr2017/ts-deepcopy
   * @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
   */
  public static deepCopy<T>(target: T): T {
    if (target === null) {
      return target
    }
    if (target instanceof Date) {
      return new Date(target.getTime()) as any
    }
    // First part is for array and second part is for Realm.Collection
    // if (target instanceof Array || typeof (target as any).type === 'string') {
    if (typeof target === 'object') {
      if (typeof target[Symbol.iterator] === 'function') {
        const cp = [] as any[]
        if ((target as any as any[]).length > 0) {
          for (const arrayMember of target as any as any[]) {
            cp.push(ObjectHelper.deepCopy(arrayMember))
          }
        }
        return cp as any as T
      } else {
        const targetKeys = Object.keys(target)
        const cp = {}
        if (targetKeys.length > 0) {
          for (const key of targetKeys) {
            cp[key] = ObjectHelper.deepCopy(target[key])
          }
        }
        return cp as T
      }
    }
    // Means that object is atomic
    return target
  }
}

how can i fix this ts error :

TS2538: Type 'symbol' cannot be used as an index type.

@MohammadFakhreddin
Copy link

MohammadFakhreddin commented Jan 27, 2020

I guess its related to your typescript version because it works fine in Typescript "3.5.3". If you are not using something like realmdb original solution works fine. But for quick workaround you can trick your compiler in the following way:

(Symbol as any).iterator

I updated the posted code as well

@arg23
Copy link

arg23 commented Feb 3, 2020

Thank you so much!!!!

@erikvullings
Copy link
Author

@fvigotti
Indeed, the ObjectHelper is missing part of the type info. However, this can be easily added, as you can see here.

@vroshupkin
Copy link

vroshupkin commented Jan 31, 2023

This working with array, objects and persist datatype

function deepCopyObj<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)) as T; }

@dominikbrazdil
Copy link

I faced an issue when cloning object which had empty objects inside - the empty objects were only passed by reference and therefore any change to the new object affected the old one. I removed the 'target !== {}' to make it work for me. @erikvullings was there any other reason except from optimization for having the condition there?

@erikvullings
Copy link
Author

This working with array, objects and persist datatype

function deepCopyObj<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)) as T; }

True, but it is a rather expensive operation, and it will fail on circular objects, i.e. objects referring to itself.

@erikvullings
Copy link
Author

I faced an issue when cloning object which had empty objects inside - the empty objects were only passed by reference and therefore any change to the new object affected the old one. I removed the 'target !== {}' to make it work for me. @erikvullings was there any other reason except from optimization for having the condition there?

No, not really - this copy/clone function is for basic usage only. I've also ported it to a more elaborate version that takes care of several other issues too, deep-copy-ts. Or you can get it from npm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment