What is the reason a type is able to cast to an indexed collection when it is inferred, while an explicit type that seems identical is

I am puzzled by why my inferred types are considered as instances of my more general collection type while my explicit types are not.

My goal was to:

  • Have a specific part of my application work with tightly defined collections (e.g., IParents vs IBosses).

  • Have another part of my application work more broadly with the same objects (as IPeople).

I expected that my explicit types would not be accepted as instances of the general indexed collection type. However, it surprised me that my inferred types were accepted. I thought the inferred type would behave similarly to my explicit type.

Do inferred types also automatically have an indexer? This aspect is not mentioned when describing the type in tooltips.

interface IPerson {
    name: string
}

let personA: IPerson = { name: "X" }
let personB: IPerson = { name: "Y" }

// Indexed person collection
interface IPeople {
    [id: string]: IPerson
}

// Explicit person collections
interface IParents {
    mother: IPerson
    father: IPerson
}

interface IBosses {
    manager: IPerson
    director: IPerson
}

// Explicitly-typed instances

let objPeople: IPeople = {
    x: personA,
    y: personB
}

let objParents: IParents = {
    mother: personA,
    father: personB
}

let objBosses: IBosses = {
    manager: personA,
    director: personB
}

// Inferred-typed instances

// Inferred type is { mother: IPerson, father: IPerson } ??
let objInferredParents = {
    mother: personA,
    father: personB,
}

// Inferred type is { manager: IPerson, director: IPerson } ??
let objInferredBosses = {
    manager: personA,
    director: personB,
}

// I want to work elsewhere with the specific types but have this be able to process them all
function processPeople(col: IPeople) {
    // NOP
}

processPeople(objPeople)

// The explicit types are NOT assignable to IPeople
//   "Argument of type 'IParents' is not assignable to parameter of type 'IPeople'.
//      Index signature is missing in type 'IParents'."
processPeople(objParents) // ERROR
processPeople(objBosses) // ERROR

// The inferred types ARE assignable to IPeople
processPeople(objInferredParents)
processPeople(objInferredBosses)

TypeScript playground

Answer №1

IPeople provides the flexibility to index using any key due to its index signature. This means that processPeople can access keys on an object even if it was not originally designed for that (for example, accessing mother on IBosses)

To ensure that the parameter contains all properties of type IPerson, you can use a generic type parameter:

function processPeople<T extends Record<keyof T, IPerson>>(col: T) {
    // No Operation
}

processPeople(objPeople)

processPeople(objParents) // Valid
processPeople(objBosses) // Valid

processPeople(objInferredParents)
processPeople(objInferredBosses)

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

The challenge of handling Set type in TypeScript errors

I'm currently facing two errors while trying to convert a function to TypeScript. The issue lies with the parameters, which are of type Set import type {Set} from 'typescript' function union<T>(setA: Set<T>, setB: Set<T>) ...

What could be the reason for the file element being undefined in the context menu?

I am currently working on rebuilding my context menu for the second time today. I am encountering an issue with an undefined value of my file element, which is preventing me from deleting, renaming, or performing any other actions. HTML <mat-list-item ...

Is there a way to both add a property and extend an interface or type simultaneously, without resorting to using ts-ignore or casting with "as"?

In my quest to enhance an HTMLElement, I am aiming to introduce a new property to it. type HTMLElementWeighted = HTMLElement & {weight : number} function convertElementToWeighted(element : HTMLElement, weight : number) : HTMLElementWeighted { elemen ...

The npm script for running Protractor is encountering an error

Currently, I am facing an issue while trying to execute the conf.js file using an npm script. The conf.js file is generated within the JSFilesRepo/config folder after running the tsc command as I am utilizing TypeScript in conjunction with protractor-jasmi ...

TypeScript 1.6 warning: Component XXX cannot be instantiated or called as a JSX element

var CommentList = React.createClass({ render: function () { return ( <div className="commentList"> Hello there! I am the CommentList component. </div> ); } }); var ...

Why are my values not being applied to the model class in Angular 7?

I'm currently developing an online shopping website where I have defined my order Model class as shown below: import { User } from './user.model'; export class Order { constructor(){} amount: Number = 0; status: String = ""; date: ...

Is it possible to utilize pinia getter as the initial parameter in Vue3's watch function?

Issue Recap In Vue3, can Pinia getters be utilized as a watch target's first argument? System Details Vue version: 3.2.13 Pinia version: 2.1.4 TypeScript version: 4.5.5 Problem Description An error was encountered when attempting to reference the ...

What is the reason for Google Chrome extension popup HTML automatically adding background.js and content.js files?

While using webpack 5 to bundle my Google Chrome extension, I encountered an issue with the output popup HTML. It seems to include references to background.js and content.js even though I did not specify these references anywhere in the configuration file. ...

What is the secret to getting this nested observable to function correctly?

Currently, I am working on an autocomplete feature that dynamically filters a list of meals based on the user's input: export class MealAutocompleteComponent { mealCtrl = new FormControl() filteredMeals: Observable<Array<Meal>> live ...

Specifying the type of "this" within the function body

After going through the typescript documentation, I came across an example that explains how to use type with this in a callback function. I am hoping someone can assist me in comprehending how this callback will operate. interface DB { filterUsers(fil ...

Guide on invoking child components' functions from the parent component in Angular 6

Main Component import { Component } from '@angular/core'; import { DisplayComponent } from './display.component'; @Component({ selector: 'my-app', template: ` <button (click)="submit()">Call Child Com ...

Tips on implementing mongoose 'Query<any>' types

I have been exploring ways to incorporate a cache layer into my TypeScript project. Recently, I came across an insightful article on Medium titled How to add a Redis cache layer to Mongoose in Node.js The author demonstrates adding a caching function to m ...

How can you loop through an array of objects in TypeScript without relying on the traditional forEach

Currently, I'm working on an array of objects with the following structure. [ { "matListParent": "CH", "dParent": "CUST1", "isAllSelected": true, "childItems&qu ...

The argument of type 'NextRouter' cannot be assigned to the parameter of type 'Props' in this scenario

In my component, I am initializing a Formik form by calling a function and passing the next/router object. This is how it looks: export default function Reset() { const router = useRouter(); const formik = useFormik(RecoverForm(router)); return ( ...

Mastering the art of typing a member of an abstract generic class in Typescript with consideration for potential additional implementations

It's quite challenging to put into words, but essentially I aim to create a base abstract class outlining an abstract interface that must vary based on whether a derived class implements a specific interface or not. Here is a TypeScript playground de ...

What is the issue when using TypeScript if my class contains private properties while the object I provide contains public properties?

I am currently facing an issue while attempting to create a typescript class with private properties that are initialized in the constructor using an object. Unfortunately, I keep encountering an error message stating: "error TS2345: Argument of type &apos ...

Guide to implementing ion-toggle for notifications with Ionic 2 and Angular 2

Currently, I am using a toggle icon to set the notification as active or inactive. The response is obtained from a GET call. In the GET call, the notification value is either 0 or 1, but in my TypeScript file, I am using noteValue as boolean, which means w ...

How to open a print preview in a new tab using Angular 4

Currently, I am attempting to implement print functionality in Angular 4. My goal is to have the print preview automatically open in a new tab along with the print popup window. I'm struggling to find a way to pass data from the parent window to the c ...

Achieving the desired type of value within a nested record

I am trying to create a unique function that can manipulate values of a nested Record, based on the collection name. However, in my current implementation, I am facing difficulty in attaining the value type: type Player = { name:string } type Point = { x:n ...

The JSON object, which has been converted into a string and sent over the network,

Attempting to set up a websocket server using TypeScript in Node.js, the following code was used: ws.on('message', (msg: string) => { console.log("got message:" + msg); const m = JSON.parse(msg); console.log(m); ...