Why do certain symbols like <> fail to function properly when used in return type checking?

interface TestInterface {
    id: number
    name: string
}

function temp1(): Pick<TestInterface, "id"> {
    return {
        id: 123,
        name: "projectName", // Error: Type '{ id: number; name: string; }' is not assignable to type 'Pick<TestInterface, "id">'.
    }
}

function temp2(): Pick<TestInterface, "id"> {
    const data = {
        id: 123,
        name: "projectName",
    }
    return data // No error
}

This code has been causing confusion for a while...

Have I overlooked something?

If it's working fine, could someone please explain why?

Running Typescript version: 4.3.5

Answer №1

Discover more about this language feature in-depth at: https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks

In essence, TypeScript demonstrates intelligence. In the first scenario, although the object technically aligns with the interface, TypeScript understands that you likely did not intend to include an extra property.

In the second instance, even though the object may have surplus properties, if it matches the return type, TypeScript allows it so that we do not have to unnecessarily duplicate and modify the object.

If there is a necessity for the property to be absent:

To eliminate a property:

Interface & { prop?: never }

To exclude all properties except one (as achieved with Pick), refer to the usage of Exactly:

Why are excess properties allowed in Typescript when all properties in an interface are optional?

Answer №2

Answers to Add

In addition to the comprehensive response from @DemiPixel; after examining the referenced documentation:

Object literals are subject to special treatment and undergo excess property checking when assigned to other variables or passed as arguments. If an object literal has properties not found in the "target type," an error will occur.

This concept is further supported by the detailed error message you encountered while running tsc:

test.ts:9:9 - error TS2322: Type '{ id: number; name: string; }' is not assignable to type 'Pick<TestInterface, "id">'.
  Object literal may only specify known properties, and 'name' does not exist in type 'Pick<TestInterface, "id">'.

9         name: "projectName",
          ~~~~~~~~~~~~~~~~~~~
Found 1 error.

General Observations on Type Systems

In contemplating this matter, I wanted to share my perspective on type systems and why I believe the level of flexibility (except for object literals) makes sense.

From my experience, types generally define the inherent capabilities of an instance but do not necessarily restrict them. For example, consider reproducing the second function from your initial code:

interface TestInterface {
    id: number
    name: string
}

function tmp2(): Pick<TestInterface, "id"> {
    const data = {
        id: 123,
        name: "projectName",
    }
    return data
}

When invoking this function, TypeScript mandates that the returned value is capable of responding to accessing the id property at a minimum (specifically, of type { id: number }).

As demonstrated above, if we have:

const result = tmp2()
console.log(result.id)
console.log(result.name)

The attempt to use result.name will result in an error:

test.ts:16:20 - error TS2339: Property 'name' does not exist on type 'Pick<TestInterface, "id">'.

16 console.log(result.name)
                      ~~~~

Found 1 error.

At a fundamental level, it seems logical to constrain the value returned within the function, which appears to be the safest approach. However, TypeScript essentially disregards any properties beyond those specified in the return type, rendering any access to such properties illegal.

Transpilation Challenges and Object Literals

Now, turning to the specific scenario of returning an object literal; one could speculate that this restriction was implemented to address potential issues when calling transpiled TypeScript code from JavaScript. Consider a situation where your code is exported from a module and then imported and utilized in the following manner:

import tmp2 from './test'

const result = tmp2()
console.log(result.id)
console.log(result.name)

Similar to before, TypeScript views result as { id: number }, resulting in the same error. Nonetheless, it is entirely permissible to import the identical transpiled module (remember, it's just a JavaScript module) from JavaScript using the same syntax, yielding:

123
projectName

Hence, this scrutiny of object literals for excess properties prevents such usage scenarios. In essence, object literals represent the sole instances where TypeScript can effectively apply this type of check; other instances originate from external sources during runtime, such as the network, storage, or user input. Static type checks during transpilation cannot safeguard against these containing surplus properties.

Conclusion

In light of both the theoretical soundness of a comprehensive type system and the hybrid nature intrinsic to TypeScript transpilation, is it essential to confine the definition of object literals to exact properties expected for assignment or usage? The necessity may hinge on the likelihood of utilizing parts of your project's transpiled output in a native JavaScript context. I argue that for the majority of TypeScript projects, reliance solely on TypeScript without such restrictions would suffice.

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 process for incorporating a custom type into Next.js's NextApiRequest object?

I have implemented a middleware to verify JWT tokens for API requests in Next.js. The middleware is written in TypeScript, but I encountered a type error. Below is my code snippet: import { verifyJwtToken } from "../utils/verifyJwtToken.js"; imp ...

Navigating dropdown list items using the keyboard in TypeScript is a breeze

I've encountered a bug while working on my open-source project. The issue is with navigating the dropdown list items using the keyboard (arrow key/tab). I have implemented the keyboard-navigation logic, but I'm unsure of how to make it work corre ...

Exploring nested keys within JSON data using TypeScript

I am currently working with this object structure { "Monday": [ { "morning": [ { "start_time": "02:00", "end_time": "07:3 ...

Dynamically loading classes in TypeScript without using default export

Is there a way to dynamically load classes in TypeScript without using a default export method? I have managed to make it work partly, but I am looking for a solution that doesn't require a default export: export default class Test extends Base { ... ...

Is there a way to integrate the AuthState TypeScript Interface into the react-oidc-context Node package for testing in Next.js?

We are currently working on a Next.js application that utilizes the react-oidc-context Node module for authentication with ADFS. During our testing phase using Vitest, we encountered the following error: TypeError: Cannot read properties of undefined (rea ...

webpack-dev-server encountered an issue: The module 'URLSearchParams' (imported as 'URLSearchParams') could not be located within the 'url' module

I keep encountering a compilation error with the webpack-serve command, which displays the following message: 'URLSearchParams' (imported as 'URLSearchParams') was not found in 'url'. My setup involves utilizing an Apollo ser ...

establish the data type for the key values when using Object.entries in TypeScript

Task Description: I have a set of different areas that need to undergo processing based on their type using the function areaProcessor. Specifically, only areas classified as 'toCreate' or 'toRemove' should be processed. type AreaType ...

Tips for verifying Angular material input for Year

Users should only be able to enter valid years (e.g., 2020) in the Purchase Year form field. An error message should display if an invalid number or text is entered. While many suggest using a datepicker, I specifically need users to only input the year v ...

Error Alert: Redundant Identifier in Angular 2 TypeScript Documents

After following the Angular2 TS Quickstart guide, I noticed duplicate files scattered across various folders in my project. For browser: typings/browser node_modules/angular2/typings/browser Regarding es6-shim: node_modules/angular2/typings/es6-shi ...

Searching for films directed or produced by Michael Bay using Typegoose

I am attempting to retrieve all movies from the getMovies endpoint that were either directed or produced by Michael Bay. async getMovies(user: User): Promise<Movies[]> { return await this.movieModel .find({ $or: [{ director: user.name }, { p ...

angular http fails to verify authorization header

My backend is set up in Node.js with Express and Sequelize. When I make a request to retrieve all my product types using Postman, everything works fine as shown in this image: postman http request and header However, when I try to make the same request f ...

Formatting code in Visual Studio 2017 for TypeScript files

When attempting to format my TypeScript Code using Ctrl+K+D, nothing seems to happen. Strangely, it works fine with all other code files. What could I be doing incorrectly? ...

Using TypeScript to assign values to object properties

In myInterfaces.ts, I have defined a class that I want to export: export class SettingsObj{ lang : string; size : number; } Now I need to reference this class in another file named myConfig.ts in order to type a property value for an object called CO ...

Tips for incorporating a versatile arrow function as an interface property in Typescript within React JSX

I'm in the process of creating an interface PromptInput { key: string, title: string, customInput?: <T>(value: T, onChange: (newValue: T) => void) => React.ReactNode; } I need the types of value and newValue to match, but they can b ...

Using Javascript to parse SOAP responses

Currently, I am working on a Meteor application that requires data consumption from both REST and SOAP APIs. The SOAP service is accessed using the soap package, which functions properly. However, I am facing challenges with the format of the returned data ...

How to eliminate all special characters from a text in Angular

Suppose I receive a string containing special characters that needs to be transformed using filter/pipe, with the additional requirement of capitalizing the first letter of each word. For instance, transforming "@!₪ test stri&!ng₪" into "Test Stri ...

The Angular Library encountered an error stating that the export 'X' imported as 'X' could not be found in 'my-pkg/my-lib'. It is possible that the library's exports include MyLibComponent, MyLibModule, and MyLibService

I have been attempting to bundle a group of classes into an Angular Library. I diligently followed the instructions provided at: https://angular.io/guide/creating-libraries: ng new my-pkg --no-create-application cd my-pkg ng generate library my-lib Subseq ...

Implementing updates to a particular value in a sub-document in Cosmos DB using Azure Function and TypeScript

I am looking to update a specific value called statutProduit in a sub-document residing within a document collection in Azure Cosmos DB. This will be accomplished using an HttpTrigger Azure Function that requires two parameters: the document ID (id) and th ...

Having trouble assigning the class property in Angular 5

Upon loading the page, a list of products is retrieved from an external JSON source. Each product in the list has a corresponding BUY button displayed alongside it, with the ID of the respective product assigned to the button. The intention is that when a ...

Angular2 form builder generating dynamic forms based on results of asynchronous calls

When creating my form, I encountered a challenge with passing the results of an asynchronous call to the form builder. This is what I have attempted: export class PerformInspectionPage implements OnInit { checklists: any; inspectionform: FormGroup; n ...