The shape-matching subset functionality in Typescript is experiencing issues

One of the key principles of TypeScript is that type checking focuses on the structure of values, a concept known as duck typing or structural typing. This means that only a subset of an object's fields needs to match for it to be considered compatible.

For instance, in TypeScript, the following code is valid:

interface Point {
  x: number;
  y: number;
}

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"

However, if you directly call the logPoint function with an object literal, TypeScript will raise a warning:

logPoint({ x: 33, y: 3, width: 30, height: 80 }); // error

So, why does subset type-matching work in the first case but not in the second?

Links:

Reference on structural type system

First example

Second example

Answer №1

Typescript's strictness levels vary depending on the context in which you are working.

When you define an object with explicit types, Typescript enforces strict rules and does not allow any additional properties to be added. This explains why your second example triggers an error: in the given context, the object can only be of type Point, but you have included extra properties which violate this rule.

Another instance of this strict checking is demonstrated below. Even though I have specified that a Point object is being created, it includes unnecessary details:

const rect: Point = { x: 33, y: 3, width: 30, height: 80 };

In contrast, your first example does not face the same level of strictness. Since the object creation line lacks a specific type declaration, Typescript assigns a generic type

{ x: number, y: number, width: number, height: number }
. Consequently, when passing this object as an argument, Typescript focuses on whether the types match rather than strictly adhering to the predefined structure.

Answer №2

@jonrsharpe is absolutely correct

This concept is referred to as: excess property check

When assigning object literals to variables or passing them as arguments, they undergo special treatment known as excess property checking. If an object literal contains properties not present in the "target type," an error will occur:

If you still prefer using references, there is a workaround available:

interface Point {
  x: number;
  y: number;
}

type IsPoint<T> = T extends Point ? Point extends T ? T : never : never;

function logPoint<T>(p: IsPoint<T>) {
  console.log(`${p.x}, ${p.y}`);
}

const rect = { x: 33, y: 3, width: 30, height: 80 };
const rect2 = { x: 33, y: 3};

logPoint(rect); // This will result in an error, as Point does not include width and height properties

logPoint(rect2) // This is fine 

logPoint({ x: 33, y: 3, width: 30, height: 80 }); // Will also generate an error

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

Tips for explaining the structure of a basic Just functor in TypeScript

I am embarking on my first attempt to create a simple interface in TypeScript, and I find myself questioning every step along the way. The core question that troubles me is: How can I best describe this straightforward Jest matcher extension? /** * @par ...

What are the steps to implement a Bottom Navigation bar in iOS and a Top Navigation bar in Android using NativeScript Angular?

For my project, I decided to utilize the tns-template-tab-navigation-ng template. I am currently working on creating a WhatsApp clone, and in iOS, it features a bottom navigation bar while in Android, it has a top navigation bar. I am looking for guidance ...

Anyone have any suggestions on how to resolve the issue with vertical tabs in material UI while using react.js?

I'm working on integrating a vertical tab using material UI in react.js, but I'm facing an issue where the tabs are not appearing. Here is the snippet of my code: Javascript: const [value, setValue] = useState(0); const handleChange1 = (event ...

TS2304 error: 'Promise' is nowhere to be found

Hey everyone, I've exhausted all the solutions available on stackoverflow with no luck. So here's my question. tsconfig.json { "version":"2.13.0", "compilerOptions": { "target": "es5", "module": "commonjs", "sourceMap": true, ...

The challenge of extending a TypeScript generic to accept an Array type with unrelated elements

I have a function that resembles the following mock: // All properties in this type are optional. interface MyType { a?: string } // The return result type of `cb` is kept as the final result type. const f = <T extends ReadonlyArray<MyType>> ...

Can I exclusively utilize named exports in a NextJS project?

Heads up: This is not a repeat of the issue raised on The default export is not a React Component in page: "/" NextJS I'm specifically seeking help with named exports! I am aware that I could switch to using default exports. In my NextJS ap ...

Return a potential undefined output

I am working with a variable called root which could potentially be undefined. Its value is only determined at runtime. const root = resolvedRoot || await this.fileSystem.getCurrentUserHome(); console.log('root.uri = ' + root.uri); The existenc ...

Creating a TypeScript client using NSwag with named properties: A step-by-step guide

Our TypeScript client is created from a swagger interface using NSwag. The resulting client code typically looks like this: client.EndPointFoo(arg1, arg2, arg3, ...) However, we encounter issues when NSwag changes the order of arguments in response to mo ...

Tips for displaying an associative object array as td elements within a tbody in Nuxt

I'm having trouble displaying the property of an associative object array in my code. I attempted to utilize a v-for loop and wanted to showcase the property information within the td elements of a tbody. I am aware that v-data-table components have a ...

The usage of the import statement outside a module is not permitted in a serverless Node application

I am currently in the process of migrating a serverless AWS lambda microservices API to TypeScript. My goal is to retain the existing JavaScript files while incorporating more TypeScript files as we progress. However, I am encountering difficulties with co ...

The ngx-datatable encountered a resolution issue with its dependency tree and was unable to resolve it

I've been trying to incorporate ngx-datatables into an Angular 12 project by running the command npm install @swimlane/ngx-datatable. However, after installation, I encountered the following Errors: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to r ...

Tips for resolving the error message "Nextjs with Typescript: 'describe' is not defined"

I am facing some obstacles while trying to compile my Nextjs project for production. Here is the list of errors that I encountered: ./components/Layout/Header/Header.test.tsx 6:1 Error: 'describe' is not defined. no-undef 7:20 Error: 'jes ...

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 ...

Break up every word into its own separate <span>

I am currently facing an issue with displaying an array of strings in HTML using spans. These spans are wrapped inside a contenteditable div. The problem arises when a user tries to add new words, as the browser tends to add them to the nearest existing sp ...

Guide on deploying a web application using Socket.io and SvelteKit on Vercel

Currently, I'm developing a multiplayer .io-style game that utilizes both Socket.io and SvelteKit. While testing the project on a local development server, everything runs smoothly. However, upon deploying to Vercel, an error message stating Failed to ...

Explain to me the process of passing functions in TypeScript

class Testing { number = 0; t3: T3; constructor() { this.t3 = new T3(this.output); } output() { console.log(this.number); } } class T3 { constructor(private output: any) { } printOutput() { ...

Troubleshooting: Imported Variable in Angular 2+ Throwing Module Not Found Error

During my testing process, I encountered an issue when trying to require a .json file with data to perform checks on. Despite passing the string indicating where to find the file into the require function, it seems to be unsuccessful... Success: const da ...

What is the process of converting an Array that is nested within an Object?

I am facing compile errors while attempting to convert an Object containing an Array of Objects. Here is the initial structure: export interface CourseRaw { metaInfo: MetaInfoRaw; gameCode: number; gameText: string; images?: ImageRaw[]; // Having ...

Combine the remaining bars by stacking the highest one on top in Highchart

Making use of stacking to display the highest value as the longest column/bar, with smaller values being merged within the highest one, can create a more visually appealing stack chart. For example, when looking at Arsenal with values of 14 and 3, ideally ...

Different ways to separate an axios call into a distinct method with vuex and typescript

I have been working on organizing my code in Vuex actions to improve readability and efficiency. Specifically, I want to extract the axios call into its own method, but I haven't been successful so far. Below is a snippet of my code: async updateProf ...