A secure function that accepts a partial object as input and automatically deduces the values of the object to be returned (similar to the functionality of Object.freeze

Hello Typescript experts,

I'm looking for some advice on a TypeScript challenge I've come across.

My goal is to create a function that mimics the return type of Object.freeze, but with enhanced intellisense and type safety for the object being passed as a parameter. The objective is to have the inferred type for the group variable in the provided code snippet below be { id: "1" } rather than { id: string }, while also ensuring proper type checking for keys and values.

interface Group{
    id: string;
}

const myFunction = (group: Partial<Group>) => group;

const group = myFunction({id: "1"})
// ^ typeof group = {id: "1"} and not {id:"string"}

Although using Object.freeze provides the desired return type, there is no type-safety or intellisense for the object passed as a parameter.

const group = Object.freeze({id: "1"})
// ^ typeof group = {id: "1"} which is what I want

Is it possible to achieve this functionality in TypeScript?

Unfortunately, combining Object.freeze with Partial does not yield the correct return type:

const myFunction = (group: Partial<Group>) => Object.freeze(group)

const group = myFunction({id: "1"})
// ^ typeof group = {id: string} but I lack intellisense

Answer №1

New Feature in Typescript 5.0

One of the exciting additions in Typescript 5.0 is the introduction of const type parameters. This feature allows for a more straightforward approach to defining functions:

const myFunction = <const T extends Partial<Group>>(group: T) => group;

Check out this playground to see it in action.

Typescript version < 5.0

Prior to Typescript 5.0, achieving similar functionality was more complex. One workaround involved using Object.freeze to create constant types. Here's an example of how you could define such a function:

freeze<T extends {[idx: string]: U | null | undefined | object}, U extends string | bigint | number | boolean | symbol>(o: T): Readonly<T>;

The use of two generic parameters helps TypeScript infer types correctly within the function.

const myFunction = <
  T extends Partial<{ [K in keyof Group]: U[K] }>,
  U extends Group,
>(
  group: T,
): Readonly<T> => group;

By including both T and U, we aid TypeScript in understanding the properties of T.

If inheritance support is required for subclasses extending a base class, the function can be modified as follows:

class BaseEntity {
  id: string;
}

class Group extends BaseEntity {
  phone: number;
}

type EntityConstructor<E extends BaseEntity> = new (...args: [any]) => E;

const _myFunction2 =
  <ExtendedEntity extends BaseEntity>(
    _entity: EntityConstructor<ExtendedEntity>,
  ) =>
  <T extends Partial<{ [K in keyof U]: U[K] }>, U extends ExtendedEntity>(
    group: T,
  ): Readonly<T> =>
    group;

const group3 = _myFunction2(Group)({ id: '1', phone: 12 });

It's important to note that passing (Group, {}) won't work due to TypeScript needing a specific definition for ExtendedEntity.

For a closer look at these concepts, visit this playground.

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

Loop through a collection of elements of a certain kind and selectively transfer only certain items to a different collection of a different kind

When working with typescript, I am faced with the challenge of dealing with two arrays: interface IFirst{ name: string; age: number } interface ISecond { nickName: string; lastName: string; } myFirstArray: IFirst[]; mySecondArray: ISe ...

Why does TypeScript trigger an ESLint error when using `extend` on a template string?

I am looking to create a TrimStart type in the following way: type TrimStart<T extends string> = T extends ` ${infer Rest}` ? TrimStart<Rest> : T; type TT = TrimStart<' Vue React Angular'>; // 'Vue React Angular' H ...

Intellisense in VS Code is failing to work properly in a TypeScript project built with Next.js and using Jest and Cypress. However, despite this issue,

I'm in the process of setting up a brand new repository to kick off a fresh project using Next.js with TypeScript. I've integrated Jest and Cypress successfully, as all my tests are passing without any issues. However, my VSCode is still flagging ...

Unable to execute V-model unit tests accurately

I've been puzzling over why I can't seem to successfully test a V-model and what mistake I might be making. Here's my straightforward component: <template> <p>Hello counter!! {{ modelValue }}</p> <button type="b ...

The functionality of the list feature seems to be malfunctioning, as it is appearing in a strange manner when displayed in console.log

When I tried to use a list to store data and display it later, I encountered the following issues: Using ListOfName.lenth resulted in [Photo of console][1]. However, using Console.log(ListOfName) displayed [Photo of List in console][2]. [1]: https://i.sst ...

Iterating through a for loop in Angular2 to send multiple GET requests to a Django backend

Currently, I'm facing a challenge with performing multiple GET requests using Angular2 within a Django/Python environment. After successfully making an API request and retrieving a list of users to determine the current user's ID, I utilize a .f ...

How can I use React to create a dictionary that modifies several values depending on a single property?

I am looking for a more efficient way to handle the following code in my component example. I have a list component that can display different types of animals such as dogs, cats, and bunnies with specific visual changes and functionality from the API. exp ...

Exploring the DynamoDB List Data Type

Currently, I am working on an angular 8 application where I have chosen to store JSON data in a list data type within DynamoDB. Inserting records and querying the table for data has been smooth sailing so far. However, I have run into some challenges when ...

Guidelines for simulating ActivatedRouteSnapshot in observable testing situations

I am currently testing an observable feature from a service in Angular. This particular observable will output a boolean value based on the queryParam provided. For effective testing of this observable, it is essential to mock the queryParam value. Howev ...

transferring libraries via functions in TypeScript

As I work on developing my app, I have decided to implement the dependency-injection pattern. In passing mongoose and config libraries as parameters, I encountered an issue with the config library. Specifically, when hovering over config.get('dbUri&ap ...

Update the checkbox value to a string

I have come across many questions regarding changing the value of a checkbox to a string, but I am still facing difficulties. Specifically, I need to change the value from true - false to 'A' - 'B'. My code is based on reactive forms in ...

Updating Angular to switch out the HTML content with a direct hyperlink to a specific section within a

A particular component on my website displays comments that contain HTML formatting, including a footer with information such as the author's name and date. I want to enhance this by turning the contents of the footer into clickable anchor links that ...

The Google APIs sheet API is throwing an error message stating "Invalid grant: account not found"

I need to retrieve data from a spreadsheet using the Sheet API. After setting up a project in Google Cloud Platform and creating a service account, I granted the account permission to edit the spreadsheet. I then downloaded the credentials in JSON format. ...

Creating an array of custom objects in Typescript involves declaring a class that represents the custom

module NamespaceX{ interface Serializable<T> { deserialize(input: Object): T; } export class CustomClass implements Serializable<CustomClass>{ private property1: number; private property2:string; con ...

Is there a way to include a query directly as a string in Drivine and Neo4j, instead of using a separate file?

My current setup involves utilizing Drivine in conjunction with Neo4j. In the provided example, there is a demonstration of injecting a query sourced from a separate file. I am curious to learn how I can directly inline a query as a string instead? ...

Using custom Components to accept HTML input

I have recently developed a custom component to arrange content within IonCardContent. It has been effective for my current requirements: interface ContainerProps { position?: string; content?: string, colour?: string; custClass?: string; } ...

Using the spread operator in the console.log function is successful, but encountering issues when attempting to assign or return it in a

Currently facing an issue with a spread operator that's really getting on my nerves. Despite searching extensively, I haven't found a solution yet. Whenever I utilize console.log(...val), it displays the data flawlessly without any errors. Howev ...

How can I properly containerize an Express Gatsby application with Docker?

SOLUTION: I am currently working on a project involving an express-gatsby app that needs to be built and deployed using GitHub Actions. To deploy it on Heroku, I have learned that containerizing the app is necessary. As a result, I have created a Dockerfil ...

Converting JavaScript code containing ?? to TypeScript is not feasible

I've encountered a dilemma while attempting to convert several JS files with double question marks to TypeScript using tsc. Unfortunately, the tsc compiler does not recognize ??. For example: this.x = typeof params.x == "string" ? this._processStrin ...

Progress Bar Modules

I am currently working on creating a customizable animated progress bar that can be utilized as follows: <bar [type]="'health'" [percentage]="'80'"></bar> It is functional up to the point where I need to adjust different p ...