Is there a way to create a clone function in TypeScript that can make readonly properties editable?

Exploring the concept of reading data in typescript and creating a clone function:

class Data {
    readonly foo: string;
}

const ro: Data = {
    foo: 'bar'
}
// In what way can we define clone to enable it to return writable data?
declare function createWritableClone<T>(val: T): T;

const rw = createWritableClone(ro);
// How should we enable the properties of rw to be writable?
rw.foo = 'updated';

What is the proper method for declaring the clone function to ensure that the object returned has writable properties?

Answer №1

Looking for a more modern approach to achieving this task? Updating the existing answer may not be the most effective way to document it.

type Mutable<T> = {
    -readonly [P in keyof T]: T[P];
};

This method offers added advantages such as preserving optional modifiers and only needing one generic parameter.

(view code in playground)

Answer №2

Here is the current method to achieve that:

type Mutable<T extends { [x: string]: any }, K extends string> = {
    [P in K]: T[P];
}

declare function clone<T>(val: T): Mutable<T, keyof T>;

(Check out the code in playground)

This approach is further discussed in this Mapped Types syntax for removing modifiers article.


Original answer:

It seems there is no direct way to remove the readonly restriction, for instance:

declare function clone<T>(val: T): {[K in keyof T]: T[K]};
const rw = clone(ro);
rw.foo = 'changed';

This will still result in the readonly error.

However, it is possible to do the opposite by starting with a "writeable" interface and then restricting it to readonly:

interface Data {
    foo: string;
}

const ro: Readonly<Data> = {
    foo: 'bar'
}
declare function clone<T>(val: Readonly<T>): T;

const rw = clone(ro);
rw.foo = 'changed';

Note that I have changed Data from a class to an interface since it is not being used as a class in your example.
If you intend to use it as a class, you would need to instantiate it:

const ro = new Data();
ro.foo = "bar";

Or

const ro = Object.assign(new Data(), { foo: "bar" });

The previous edit involved using a bug unintentionally:

I stand corrected, you can indeed bypass the readonly modifier:

declare function clone<T>(val: T): {[K in (keyof T)]: T[K]};
const rw = clone(ro);
rw.foo = 'changed';

This works seamlessly, with the key difference being (keyof T) instead of just keyof T. There is an ongoing issue related to this on: Mapped Type bug involving modifiers, which has been acknowledged as a bug.

Answer №3

Here's a different solution that solved my problem:

const configuration = JSON.parse(JSON.stringify(require('src/data/settings.json')));

I encountered an issue where the variable configuration became read-only when using require directly.

Answer №4

Check out this TypeScript function for deep cloning an object. I found it on this website and made some tweaks:

/**
 * Deep clone an object while preserving its type.
 *
 * @param source: The object to be cloned.
 * @param preservePropertyDescriptor: Indicates whether to keep the property descriptor intact. For more info, check:
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
 * @param excludedProperties: A string or array of strings representing properties that should not be copied into the cloned object at any level.
 *
 * @returns Mutable<T> a mutable version of the input object.
 */
export const deepCopy: Function = <T>(source: T, preservePropertyDescriptor: boolean = false,
                                      ...excludedProperties: string[]): Mutable<T> => {
  return Array.isArray(source)
    ? source.map((item: T): Mutable<T> => deepCopy(item, preservePropertyDescriptor, excludedProperties))
    : source instanceof Date
     ? new Date(source.getTime())
     : source && typeof source === 'object'
       ? Object.getOwnPropertyNames(source).reduce((obj: any, prop: string): Mutable<T> => {
           if (excludedProperties.includes(prop)) { // skip property
             return obj;
           } else {
             if (preservePropertyDescriptor) {
               Object.defineProperty(obj, prop, Object.getOwnPropertyDescriptor(source, prop));
             }
             // @ts-ignore
             obj[prop] = deepCopy(source[prop], preservePropertyDescriptor, excludedProperties);
             return obj;
           }
         }, Object.create(Object.getPrototypeOf(source)))
       : source as Mutable<T>;
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Design a model class containing two arrow functions stored in variables with a default value

I am looking to create a model class with two variables (label and key) that store functions. Each function should take data as an input object. If no specific functions are specified, default functions should be used. The default label function will retur ...

Angular error TS2339: The property 'before' is not found on type 'HTMLElement' / 'HTMLTextAreaElement' / etc

Objective: My goal is to reposition a div (containing a mat-select dropdown) ABOVE a mat-card-title when the user is accessing the site from a mobile device. If the user is not on a mobile device, the div should remain in its original position to the right ...

Accessing the form element in the HTML outside of the form tag in Angular 2

I am attempting to achieve the following: <span *ngIf="heroForm?.dirty"> FOO </span> <form *ngIf="active" (ngSubmit)="onSubmit()" #heroForm="ngForm"> <div class="form-group"> <label for="name">Name</label& ...

Angular firebase Error: The parameter 'result' is missing a specified type and is implicitly assigned the 'any' type

I have encountered an issue with the code I am working on and both the result and error are throwing errors: ERROR in src/app/login/phone/phone.component.ts(48,75): error TS7006: Parameter 'result' implicitly has an 'any' type. s ...

Swap the value of a button's text using a dropdown list when clicked in VueJS with TypeScript

I have a button with a click event that opens a dropdown list. I would like for the button text to be updated and for the selected option to be removed from the dropdown list when the user makes a selection. Currently, using {{interestSortingOptions.label} ...

Enforcement of Class Initialization in Typescript 2.7

After initializing a sample project using the Angular template in Visual Studio 2017, I made sure to update the package.json file with the latest module versions. However, upon executing the npm install command and navigating to the site, an error related ...

Troubles arise when compiling TypeScript to JavaScript

I have been experimenting with TypeScript, specifically for working with classes. However, I am facing an issue after compiling my TS file into JS. Below is the TypeScript code for my class (PartenaireTSModel.ts): export namespace Partenaires { export ...

Streamlining all icons to a single downward rotation

I am currently managing a large table of "auditpoints", some of which are designated as "automated". When an auditpoint is automated, it is marked with a gear icon in the row. However, each row also receives two other icons: a pencil and a toggle button. W ...

Issues with Angular ng-bootstrap tabset component not functioning as expected

{ "name": "ModalWindow", "version": "1.0.0", "repository": { "type": "git", "url": "" }, "scripts": { "build": "webpack --mode production", "start": "webpack-dev-server --mode development --open" }, "license": "MIT", "depend ...

Adding a QR code on top of an image in a PDF using TypeScript

Incorporating TypeScript and PdfMakeWrapper library, I am creating PDFs on a website integrated with svg images and QR codes. Below is a snippet of the code in question: async generatePDF(ID_PRODUCT: string) { PdfMakeWrapper.setFonts(pdfFonts); ...

Enhancing JSON Formatting with Angular 4 and Typescript

In the process of developing my Angular 4 application, I am interfacing with a REST API through JSON requests. As I work on creating JSON objects to send via POST requests, I find myself putting in quite a bit of manual effort to construct them... I KNOW ...

TS & Angular: Unlocking the Power of Conditional Interfaces

My user component includes a variable called user, which can be either an Employee or a Student. In my HTML, I have an element {{ user.coure ?? user.department }} I'm encountering an issue in my HTML because some properties in the Employee interface ...

Issue with intrinsic attributes detected in Typescript for the component

Hey, I'm encountering an issue that says, "The type '{ data: dataProp[]; }' cannot be assigned to type 'IntrinsicAttributes & dataProp'. A property 'data' does not exist on type 'IntrinsicAttributes & dataPro ...

Angular recognizing string-array type as a string input is not correct

I am encountering a challenge with an Angular CLI component that involves working with an array of strings called "searchResult": export class ParentComponent implements OnInit { mockArray: string[] = []; searchString: string = ''; searchR ...

What is the most appropriate form to use, and what factors should be considered in determining

Incorporating generics in typescript allows me to create a generic function in the following manner: Choice 1 declare function foo1<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } Alternatively, I have the option to omit the seco ...

Exploring TypeScript and React: Redefining Type Definitions for Libraries

As I transition from JSX to TSX, a challenge has arisen: My use of a third-party library (React-Filepond) This library has multiple prop types The provided types for this library were created by an individual not affiliated with the original library (@ty ...

Convert a TypeScript array of strings to a boolean array: a step-by-step guide

Upon receiving a list of objects from the front end as: item=["false","true"] I proceed to check a column within my records to identify values containing "true" or "false" using the following code: this.records.filter(x=> items.includes(x.column)) Unf ...

Screen detection for a responsive side navigation bar

I'm currently tackling a challenge in an Angular project utilizing the Material Library. My issue lies in implementing a method to open the sidenav. By default, it should use mode="side", but for smaller devices, I need it to stick with mode="over". I ...

How can React with TypeScript dynamically extend type definitions based on component props?

Can a React component dynamically determine which props it has? For example: type BaseType = { baseProp?: ... as?: ... } type Extended = { extendedProp?: ... } <Base /> // expected props => { baseProp } <Base as={ExtendedComponent} ...

Next.js v13 and Firebase are encountering a CORS policy error which is blocking access to the site.webmanifest file

Background: I am currently developing a website using Next.js version 13 in combination with Firebase, and I have successfully deployed it on Vercel. Upon inspecting the console, I came across two CORS policy errors specifically related to my site.webmani ...