Using Typescript to create a mapped type that allows for making all properties read-only, with the exception of

I encountered a problem where I didn't want to repeatedly rewrite multiple interfaces.

My requirement is to have one interface with full writing capabilities, while also having a duplicate of that interface where all fields are set as read-only except for the ones I specifically choose to be editable.

I believe Typescript's mapped types might offer a solution for this issue.

Answer №1

export type DeepMakeReadOnly<T> = { readonly [property in keyof T]: DeepMakeReadOnly<T[property]> };
export type DeepMakeMutable<T> = { -readonly [propKey in keyof T]: DeepMakeMutable<T[propKey]> };
export type ExcludeProperties<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type CombineReadWrite<T, K extends keyof T> = DeepMakeReadOnly<ExcludeProperties<T, K>> & DeepMakeMutable<Pick<T, K>>;

// implement it this way
let obj: CombineReadWrite<Info, 'name' | 'age'>;
// name and age will be writable while the rest remain read-only

// These types can also be extended and adjusted with index signatures, optional properties, and depth levels

Answer №2

To enforce immutability in TypeScript, the Readonly utility type can be utilized:

type UserData = {
  name: string;
};

const user: UserData = {
  name: "John",
};

user.name = "Jane";
// ^ This value can be changed

type ImmutableUserData = Readonly<UserData>;

const immutableUser: ImmutableUserData = {
  name: "Jake",
};

immutableUser.name = "Emily";
// ^ ERROR! - 'name' is read-only and cannot be assigned a new value.

Answer №3

To achieve this, we can utilize utility types or mapped types directly. Here is an example:

type MakeSomePropsReadonly<A, K extends keyof A> = Readonly<Pick<A, K>> & Omit<A, K>

type Original = {
    a: string,
    b: number,
    c: boolean
}
type Example = MakeSomePropsReadonly<Original, 'a'>
const example: Example  = {
    a: 'a',
    b: 1,
    c: true
}

example.a = 'b'; // error property is readonly
example.b = 2; // not readonly, so it's okay

The

Readonly<Pick<A, K>> & Omit<A, K>
type does the following:

  • Readonly<Pick<A, K>> - creates a type with only keys picked by K that are readonly
  • & Omit<A, K> - intersect the previous type with the second part to create an object with fields different from key K

This results in combining one type with all readonly properties and another with writable ones.


We can also approach it differently by having a readonly interface and selecting which fields are writable:

type MakeSomePropsWritable<A, K extends keyof A> = {
    -readonly [Key in K]: A[Key]
} & Omit<A, K>

type Original = {
    readonly a: string,
    readonly b: number,
    readonly c: boolean
}
type Example = MakeSomePropsWritable<Original, 'a'>
const example: Example  = {
    a: 'a',
    b: 1,
    c: true
}

example.a = 'b'; // now allowed since it is set as writable
example.b = 2; // error as expected

No significant difference in this implementation apart from:

  • -readonly [Key in K]: A[Key] - removing readonly from specified fields using the - prefix

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

What is the specific purpose of the 'extend' keyword in Typescript?

Recently, I have been delving into the realm of Javascript/Typescript/React as a PHP developer. During my learning process, I encountered a few perplexing issues that I could not fully grasp. In light of this, I am reaching out to the experienced indiv ...

Data fetched using React Query

When using React Query to fetch data, the function runs smoothly. After console.logging the 'data' variable from React Query, it prints an array of objects as expected and handles states efficiently between loading, success, error. The issue ar ...

Determining the appropriate version of the types package for your needs

It has come to my attention that certain npm packages do not come with types included. Because of this, the community often creates @types/packagename to provide those types. Given that both are packages, how does one determine which version of the types ...

Find all Mondays occurring within a specified date range using Moment.js

I need to extract all Mondays within a specific date range. let start = moment(this.absence.FromDate); let end = moment(this.absence.ToDate); The user has the option to deactivate certain weekdays during this period by setting booleans. monday = true; t ...

Utilizing conditional types for type narrowing within a function's body: A comprehensive guide

I created a conditional type in my code that constrains the second argument of my class constructor based on the type of the first argument. Although the type checker correctly limits what I can pass to the constructor, I am struggling to get the compiler ...

Validation in Angular2 is activated once a user completes typing

My goal is to validate an email address with the server to check if it is already registered, but I only want this validation to occur on blur and not on every value change. I have the ability to add multiple controls to my form, and here is how I have st ...

Discover how to access JSON data using a string key in Angular 2

Trying to loop through JSON data in angular2 can be straightforward when the data is structured like this: {fileName: "XYZ"} You can simply use let data of datas to iterate over it. But things get tricky when your JSON data keys are in string format, li ...

Raycasting in Three.js is ineffective on an object in motion

Working on a project that combines three.js and typescript, I encountered an issue while attempting to color a sphere by raycasting to it. The problem arises when the object moves - the raycast doesn't seem to acknowledge the new position of the objec ...

Using the React UseEffect Hook allows for value updates to occur within the hook itself, but not within the main

I am currently utilizing a font-picker-react package to display fonts using the Google Font API. Whenever a new font is chosen from the dropdown, my goal is to update a field value accordingly. While the 'value' updates correctly within the ...

Creating a Personalized Color Palette Naming System with Material UI in TypeScript

I have been working on incorporating a custom color palette into my material ui theme. Following the guidance provided in the Material UI documentation available here Material UI Docs, I am trying to implement this feature. Here is an excerpt from my cod ...

Angular problem arises when attempting to map an array and selectively push objects into another array based on a specific condition

Setting up a cashier screen and needing an addToCart function seems pretty simple, right? However, I am encountering a strange logical error. When I click on an item to add it to the cart, my function checks if the item already exists in the array. If it d ...

Inject props into a Component nested within a Higher-Order-Component (HOC)

In attempting to grasp the concept of creating a React Higher Order Component from this particular article, I find myself struggling to fully understand and utilize this HOC. interface PopupOnHoverPropType { hoverDisplay: string; } const WithPopupOnHov ...

The function _path2.default.basename does not work when using convertapi within an Angular framework

I'm currently working on integrating the convertapi into my Angular 11 application by referencing the following documentation https://www.npmjs.com/package/convertapi My goal is to convert PDFs into images, However, I encountered an issue when tryi ...

Warning: Potential spacing issues when dynamically adjusting Material UI Grid using Typescript

When working with Typescript, I encountered an error related to spacing values: TS2322: Type 'number' is not assignable to type 'boolean | 7 | 2 | 10 | 1 | 3 | 4 | 5 | 6 | 8 | "auto" | 9 | 11 | 12'. No lint errors found Version: typesc ...

Ionic: Fixed button located at the bottom of a specific ion-slide

I've been creating a series of slides with questions, and the final slide serves as a summary of the previously answered questions. I want to ensure that the submit button is always visible at the bottom of this last slide. However, I've encounte ...

What is the recommended approach for returning two different types in a TypeScript function?

My API function currently performs a post request and returns an Observable of ModelAResponse, which is an interface I have defined. I now want to modify this function so that it can return an Observable of either ModelAResponse or ModelBResponse based on ...

Enhancing Twilio LocalVideoTrack with custom start and stop event handling

Is it possible to customize the events triggered when a video starts or stops playing? I attempted the code below, but unfortunately, it did not yield any results: this.videoTrack = screenTrack as LocalVideoTrack; this.videoTrack.stopped = function (eve ...

Issue encountered with Azure DevOps during TypeScript (TS) build due to a type mismatch error: 'false' being unable to be assigned to type 'Date'. Conversely, the build functions correctly when run locally, despite the type being defined as 'Date | boolean'

I am facing an issue with my NestJS API while trying to build it using Azure DevOps pipeline. The build fails with the following error: src/auth/auth.controller.ts(49,7): error TS2322: Type 'false' is not assignable to type 'Date'. src/ ...

Passing values in onPress method of TouchableOpacity without using arrow functions or bind function can be achieved by using JSX props. Remember not to use arrow functions in JSX props

I am working on a React Native project and I have a TouchableOpacity component in my view with an onPress method. I want to avoid using arrow functions and bind functions in the onPress method as it creates a new function every time. My goal is to pass par ...

The TypeScript in the React-Native app is lacking certain properties compared to the expected type

I recently integrated the https://github.com/react-native-community/react-native-modal library into my project and now I need to create a wrapper Modal class. Initially, I set up an Interface that extends multiple interfaces from both react-native and reac ...