Tips for distinguishing a mapped type using Pick from the original type when every property is optional

I am working with a custom type called ColumnSetting, which is a subset of another type called Column. The original Column type has most properties listed as optional:

type ColumnSetting = Pick<Column, 'colId' | 'width' | 'sort'>;

type Column = {
  colId: string,
  width?: number,
  sort?: number
  something?: string
}

Column includes more properties compared to ColumnSetting. However, TypeScript (TS) allows the use of Column in place of ColumnSetting without any issues. Is there a way to define ColumnSetting so that TS will not accept Column where ColumnSetting is expected?

Answer №1

When it comes to objects in TypeScript, they are considered open, meaning they allow for additional properties not explicitly defined in the declaration. This characteristic enables interfaces and object types to be easily extended. Without this feature, scenarios like having an interface Bar that extends Foo but is not a valid Foo would arise. However, with TypeScript, {a:string, b:string} can be assigned to {a: string} without any issues.

Sometimes there is confusion surrounding whether TypeScript types are closed, sealed, or exact. The closest concept in TypeScript to these ideas are excess property checks on object literals, which serve more as linter warnings rather than strict type safety checks.

It's essential to note that Pick will always be a supertype of T, allowing Column to be assigned to Pick despite the key K. If you want to exclude certain properties, you must do so explicitly by defining them as optional or using the never type.

To prevent unwanted keys from existing in an object, consider using ExclusivePick instead of Pick:

type ExclusivePick<T, K extends keyof T> = 
    { [P in K]: T[P] } & { [P in Exclude<keyof T, K>]?: never }

This code essentially creates an object type that includes only specified keys while prohibiting all other keys in T.

Testing out the ExclusivePick type:

type ColumnSetting = ExclusivePick<Column, 'colId' | 'width' | 'sort'>;

/* type ColumnSetting = {
    colId: string;
    width?: number | undefined;
    sort?: number | undefined;
} & {
    something?: undefined;
}*/

If attempting to assign a Column to ColumnSetting, an error will occur due to incompatible property types, emphasizing the importance of specifying allowed keys explicitly.

Playground link to code

Answer №2

This is because Typescript utilizes a concept known as structural typing.

Consider the following scenario:

type Table = {
  tableId?: string,
  dimensions?: number,
  rows?: number
}

export type TableSchema = Pick<Table, 'tableId' | 'dimensions' | 'rows'>;

declare function bar(tab: Table): void;

declare const schema: TableSchema

bar(schema); //works fine 

The types Table and TableSchema essentially represent the same structure:

{
    tableId?: string | undefined;
    dimensions?: number | undefined;
    rows?: number | undefined;
}

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

Dynamically modifying the display format of the Angular Material 2 DatePicker

I am currently utilizing Angular 2 Material's DatePicker component here, and I am interested in dynamically setting the display format such as YYYY-MM-DD or DD-MM-YYYY, among others. While there is a method to globally extend this by overriding the " ...

Why is it that TypeScript's flow analysis does not extend to the 'else' block?

Consider the code below: function f(x : number) { if (x === 1) { if (x === 2) {} // error } else { if (x === 1) {} // OK } } The compiler flags an error on x === 2. This is because if the code reaches this block, x must be ...

Ways to elegantly replace an object with thorough validation of its embedded properties

Consider the following code snippet: interface Human{ name:string age:number dimensions : { height:number width:number } } const base : Human ={ name:"Base", age:12, dimensions : { height:190, width:99 } }; const child ...

Assign a specific value to the sub-component within the grid using Angular 2+

Incorporating Angular 8 and TypeScript into my project, I have a grid that consists of various internal components, one being <ng-select/>. The data binding takes place in the child component during onInit. Upon loading and initialization of the dat ...

TypeScript encountered an unexpected { token, causing a SyntaxError

Despite multiple attempts, I can't seem to successfully run my program using the command "node main.js" as it keeps showing the error message "SyntaxError: Unexpected token {" D:\Visual Studio Code Projects\ts-hello>node main.js D:&bsol ...

Having trouble deciphering the Enum definition in the Typescript Build

One of my projects utilizes a typescript package stored in npm with all the necessary definitions: index.d.ts export declare namespace OfferCraft { enum Country { es, it, fr, uk, de } enum Brand { ...

When using expressjs and typescript, you may encounter an error stating that the type 'typeof <express.Router>' cannot be assigned to the parameter type 'RequestHandlerParams'

Working on my project using expressjs with the latest typescript definition file and typescript 2.3.4 from https://github.com/DefinitelyTyped/DefinitelyTyped. I've set up a router and want to access it from a subpath as per the official 4.x documentat ...

What could be causing NestJS/TypeORM to remove the attribute passed in during save operation?

Embarking on my Nest JS journey, I set up my first project to familiarize myself with it. Despite successfully working with the Organization entity, I encountered a roadblock when trying to create a User - organizationId IS NULL and cannot be saved. Here ...

Ways to verify whether a checkbox is selected and store the status in the LocalStorage

Hey there, I'm still new to the world of programming and currently just a junior developer. I have a list of checkboxes and I want to save any unchecked checkbox to my local storage when it's unselected. Below is a snippet of my code but I feel l ...

I wonder what the response would be to this particular inquiry

I recently had an angular interview and encountered this question. The interviewer inquired about the meaning of the following code snippet in Angular: code; <app-main [type]="text"></app-main> ...

Angular - handling Observable<T> responses when using Http.post

One issue I encountered was when trying to implement a method that returns an Observable. Within this method, I utilized http.post to send a request to the backend. My goal was to store the JSON object response in an Observable variable and return it. Howe ...

Struggling to retrieve the ID from the API within the Angular and .NET Core component

Currently, I am working on a test project to enhance my knowledge of Angular. However, I have encountered an issue where the student's id fetched from the service is null. To handle the data, I have implemented a StudentController. Below is a snippet ...

Learn the art of generating multiple dynamic functions with return values and executing them concurrently

I am currently working on a project where I need to dynamically create multiple functions and run them in parallel. My starting point is an array that contains several strings, each of which will be used as input for the functions. The number of functions ...

Generating step definitions files automatically in cucumber javascript - How is it done?

Is there a way to automatically create step definition files from feature files? I came across a solution for .Net - the plugin called specflow for Visual Studio (check out the "Generating Step Definitions" section here). Is there something similar avail ...

Revealing the Webhook URL to Users

After creating a connector app for Microsoft Teams using the yo teams command with Yeoman Generator, I encountered an issue. Upon examining the code in src\client\msteamsConnector\MsteamsConnectorConfig.tsx, I noticed that the webhook URL w ...

The Vercel/NextJS deployment does not delay the completion of the serverless function until the email is sent via Azure

Upon a user's registration, I am attempting to send a registration/account activation email. While the email sends successfully via Azure's email services when running on localhost, deployments on Vercel do not trigger the email (although the use ...

Maintaining the consistent structure of build directories within a Docker container is crucial, especially when compiling TypeScript code that excludes the test

Our application is built using TypeScript and the source code resides in the /src directory. We have tests located in the /tests directory. When we compile the code locally using TSC, the compiled files are deposited into /dist/src and /dist/test respectiv ...

Tips for maintaining the original data type while passing arguments to subsequent functions?

Is there a way to preserve generic type information when using typeof with functions or classes? For instance, in the code snippet below, variables exampleNumber and exampleString are of type Example<unknown>. How can I make them have types Example& ...

Searching for MongoDB / Mongoose - Using FindOneById with specific conditions to match the value of an array inside an object nestled within another array

Although the title of this question may be lengthy, I trust you grasp my meaning with an example. This represents my MongoDB structure: { "_id":{ "$oid":"62408e6bec1c0f7a413c093a" }, "visitors":[ { "firstSource":"12 ...

What is the best way to retrieve a value from an array of objects containing both objects and strings in TypeScript?

Consider this scenario with an array: const testData = [ { properties: { number: 1, name: 'haha' } , second: 'this type'}, ['one', 'two', 'three'], ]; The goal is to access the value of 'second&ap ...