The precise return type of a function without any extra attributes

Recently, I encountered an issue concerning the return type of the createUser function.

interface IUserModel{
    fullname: string
    username: string
    password: string
}

type IUserDto = Omit<IUserModel, 'password'>

function createUser(user: IUserModel): IUserDto {
    // in reality, this invokes a create method from a user repository
    const newUser: IUserModel = {
        fullname: user.fullname,
        username: user.username,
        password: user.password
    }
    
    /* 
     TypeScript fails to indicate that the password property should not be present in the new user object
    */
    return newUser
}

Simply returning the object literal is not satisfactory for me; I want TypeScript to alert me about any issues.

Is there a way to modify the function so it only returns exactly IUserDto without any additional properties?

Answer №1

The Omit utility type simply eliminates a specified set of properties from the existing keys of an object type. However, objects in general can contain additional properties beyond those defined by their type. Object types are not "exact" (as discussed in microsoft/TypeScript#12936); if a key is not explicitly mentioned in a type, that property is considered unspecified or unknown rather than prohibited.

Otherwise, using extended interfaces would be impossible:

interface Foo {
  x: string;
}
function processFoo(foo: Foo) {
  console.log(foo.x.toUpperCase())
};

interface Bar extends Foo {
  y: number
}
const bar: Bar = { x: "abc", y: 123 };
processFoo(bar); // Allowed because every Bar is also a Foo

In the example above, although Foo does not include the y property, it does not prevent Bar from extending it. Omitting properties from a type expands its acceptance criteria rather than limiting it.

This means an IUSerModel could still be a valid value of type

Omit<IUserModel, 'password'>
.


It is not possible to create a type that rejects all potential excess properties without exact types or some form of "rest index signature" as requested in microsoft/TypeScript#17867, among other features.

However, you can somewhat prohibit a specific excess property. You can define it as an optional property (allowing for its presence or absence) of the impossible never type (making it incredibly difficult to exist). This approach closely resembles property prohibition, with some exceptions.

As a result, we can create our custom OmitAndProhibit utility type that excludes certain properties and then reintroduces them as optional properties of type never:

type OmitAndProhibit<T, K extends string> =
  Omit<T, K> & { [P in K]?: never };

Let's demonstrate this concept:

type IUserDto = OmitAndProhibit<IUserModel, 'password'>

function createUser(user: IUserModel): IUserDto {
  const newUser: IUserModel = {
    fullname: user.fullname,
    username: user.username,
    password: user.password
  }

  return newUser; // Error!
  //   Type 'IUserModel' is not assignable to type '{ password?: undefined; }'.
}

The compiler correctly identifies the issue with newUser having a string-valued password property when it expects an optional property of type never. This aligns closely with prohibiting the property, albeit with certain edge cases to consider.

Click here to access the code on Playground

Answer №2

it is required that the instance of newUser conforms to the interface IUserDto

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

Is it possible to set specific points within a Points object in THREE.js to be transparent or invisible?

I am working with a Three.js Points object that holds information for displaying multiple points in 3D space. I am looking for a way to dynamically hide certain points, but I am uncertain of the process. The PointsMaterial used has xyz data stored in poin ...

Leveraging editor.action.insertSnippet from a different plugin

I am attempting to enhance the functionality of VS Code by adding buttons to the status bar that automatically insert code snippets. I am utilizing this Extension for this purpose. Additionally, I have configured keybindings in my keybindings.json file whi ...

Participating in consolidated data

I need to merge data from my trainings-table with my users-table. The structure of the data is as follows: - users - key1 - name - trainingIds - t_key1 - t_key3 - trainings - t_key1 - name - description - t_k ...

What sets apart an inlined return type from a return type defined in a separate type definition within a function?

I'm experiencing inconsistent behavior when I specify the return type of a function inline compared to defining it in a separate type definition. For instance: interface Foo { bar: string; } type Fooer = () => Foo; const foo1: Fooer = () =& ...

What are the best methods for testing a function containing multiple conditional statements?

I have a complex function that I want to showcase here, it's quite simple but for some reason, I'm struggling with writing unit tests for it. I don't need the exact unit test implementation, just a general approach or tips on how to handle i ...

When using Google login in React and Node.js, the requested scope may not be returned as expected

Looking to get additional scope data from Google API during login on my app. I am utilizing react-google-login to obtain a token in my React application with the following scopes: scope='https://www.googleapis.com/auth/user.birthday.read https://www. ...

Understanding the Purpose of the Pipe Function in Angular 2 and Typescript Observables

Recently, I encountered a situation where I needed to accept an Observer parameter in a function written in Typescript. I struggled to find a solution, reminding me of working with a delegate parameter in C#. The specific scenario involved adding a bookend ...

How to Eliminate Lower Borders from DataGrid Component in Material UI (mui)

I've been trying to customize the spacing between rows in a MUI Data Grid Component by overriding the default bottom border, but haven't had much success. I've experimented with different approaches such as using a theme override, adding a c ...

Challenges with importing and using jspdf and autotable-jspdf in Angular 8

Issue with Generating PDF Using Angular 8, JSPDF, and JSPDF-AutoTable I am facing a challenge with exporting/generating a PDF based on an HTML grid. I need to make some DOM changes with CSS, remove toggle buttons, alter the header, etc. However, all the s ...

Working with type-agnostic values in a type-agnostic list within a type-agnostic class using Typescript

I'm currently attempting to add a generic object to a list of other generic objects within a generic class. There seems to be an issue with the semantics, but I can't pinpoint exactly what the problem is. type EventCallback<I, O> = (event ...

How do you add a line break ( ) to a string in TypeScript?

I'm having trouble adding a line break to a concatenated string like this: - Item 1 - Item 2 var msg: string = ''; msg += '- Campo NOME é obrigatório'; msg += "\n- Campo EMAIL é obrigatório"; However, when I display ...

Getting environment variable from JSON file within Angular 4 - a step-by-step guide

I have a file named "appsettings.json" which contains configurations for a specific purpose. I want to include variables from both "environment.ts" and "environment.prod.ts" in this file and access them within the environment files. When I attempt to impo ...

Is there a way to change a .pptx document into a base64 string?

Currently, I am working on a project that involves creating an addin for Office. The challenge I am facing is opening other pptx files from within the addin. After some research, I discovered that I need to use base64 for the PowerPoint.createPresentation( ...

What is the best way to define a union type that encompasses all values within a field of an array?

I have a function that takes an array and a field parameter, but I want to restrict the field's type to a union type that describes all the values of the fields in the array. Here's an example: interface item { name: string, value: number ...

Is there a way to determine if silent login is feasible with adaljs-angular4?

I'm currently utilizing the library [email protected] for an Angular 6 project. I am attempting to achieve the following: If a silent login (login without requiring user input) with Office365 is achievable, then perform a silent login (using ...

React enforces a restriction on importing types from external directories when using TypeScript in the project

I'm facing an issue in my React project that is built with TypeScript. I am attempting to import types from a directory located outside of the React folder, but for some reason, React is not allowing this. I have integrated craco on top of Create Reac ...

Implementing a dynamic star rating system in Angular

I am working with an array of ratings that looks like this: "rating": [ { "sno": 1, "question": 13, }, { "sno": 2, ...

Using TypeScript with Nuxt/Vue.js: The 'components' property cannot be specified in an object literal as it is not recognized in the 'VueClass' type

I am puzzled by the error message I am receiving when using a decorator in conjunction with components and middleware: https://i.sstatic.net/dutqx.png When I examine the error, it states: TS2345: Argument of type '{ components: { Test: typeof Nav; } ...

Guide on setting a default value for a variable within a Typescript class

In the process of developing a component to manage information about fields for form use, I am in need of storing different data objects in order to establish generic procedures for handling the data. export class DataField<T> { /** * Field ...

React 16 exhibiting unexpected behavior with class names

I am trying to implement the classnames object into my input field within a react 16 project, completely independent of the webpack tool. const fieldClassName = classnames( formControlStyles.field, 'form-control' ) The 'form-control& ...