Utilizing the in operator for effective type guarding in Typescript

Before posting an issue on github, I wanted to get some feedback here first.

I'm currently developing a component entity system for a game using typescript. From what I've seen, component entity systems in javascript and typescript can lack type safety, so I have an idea to address this issue.

My plan is to create an Entity type for each system that relies on multiple components. Each Entity type will define the properties that entities processed by that system should have. These Entity types will then be combined into one unified Entity union type.

export type Entity =
    CameraManagerEntity |
    CollisionManagerEntity |
    HoleManagerEntity |
    ParentManagerEntity | ...

Within a system, I want to be able to assert that certain properties exist on an entity and have typescript infer the correct type based on that assertion. For instance, if the CameraManager exports:

interface CameraManagerEntity {
    foo: boolean,
    bar: number
}

And no other type in the Entity union has a "foo" parameter. My expectation is that checking for "foo" within an entity should allow access to "bar" without triggering a type error:

function processEntity(entity: Entity) {
    if ("foo" in entity) {
        return entity.bar; // <-- Type error.
    }
}

Is my approach flawed? It seems like the compiler should be able to determine the specific Entity type based on context. However, it appears that this concept doesn't align with how typescript currently functions. Are there alternative methods to achieve my goal? Appreciate any insights.

Edit: I am familiar with user-defined type guards, but I'm hoping to avoid manual guard implementation as I believe the compiler already possesses the necessary information.

Answer №1

Your desired scenario is akin to an inverted Tagged Union Type concept. In a Tagged Union Type, all members share a common field, and TypeScript discriminates based on the value of that field using 'switch'. However, in your current design, union type members are distinguished by unique fields.

I developed a generic type guard that verifies an 'entity' against a type using property presence:

if (hasKeyOf<CameraManagerEntity>(entity, 'camera')) {
  return entity.camera;
}

Below is the complete example:

interface CameraManagerEntity {
  camera: string;
}

interface CollisionManagerEntity {
  collision: string;
}

type Entity = CameraManagerEntity | CollisionManagerEntity;

// Generic type guard 
function hasKeyOf<T>(entity: any, key: string): entity is T {
  return key in entity;
}

function processEntity(entity: Entity) {
  if (hasKeyOf<CameraManagerEntity>(entity, 'camera')) {
    return entity.camera;
  } else if (hasKeyOf<CollisionManagerEntity>(entity, 'collision')) {
    return entity.collision;
  } 
}

Try it in TypeScript Playground

Alternatively, you may want to consider utilizing Tagged Union Types for your entity system, which could offer greater ease-of-use:

interface CameraManagerEntity {
  kind: 'CameraManager';
  camera: string;
}

interface CollisionManagerEntity {
  kind: 'CollisionManager';
  collision: string;
}

type Entity = CameraManagerEntity | CollisionManagerEntity;

function processEntity(entity: Entity) {
  switch (entity.kind) {
    case ('CameraManager'): return entity.camera;
    case ('CollisionManager'): return entity.collision;
  }
}

Try it in TypeScript 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

What is the best way to access dynamic URL parameters in the Next.js 13 app router from a nested server-side component?

This query pertains to the Server-Side components in Next.js 13 app router: I have a good grasp on how the slug parameter is passed to the default function in app/blog/[slug]/page.tsx: export default function Page({ params }: { params: { slug: string } }) ...

Develop a new flow by generating string literals as types from existing types

I'm running into a situation where my code snippet works perfectly in TS, but encounters errors in flow. Is there a workaround to make it function correctly in flow as well? type field = 'me' | 'you' type fieldWithKeyword = `${fiel ...

An issue has been detected with the width attribute in Typescript when using

I have a question regarding the TypeScript error related to the width property. I've created a component called ProgressBar where I'm using Stitches for styling. However, TypeScript is throwing an error even when I specify the type as ANY. impor ...

Shared validation between two input fields in Angular 2+

I have a unique task at hand. I am working on creating an input field with shared validation. The goal is to ensure that both fields are technically required, but if a user fills in their email address, then both fields become valid. Similarly, if they ent ...

Ensuring the proper export of global.d.ts in an npm package

I'm looking to release a typescript npm package with embedded types, and my file structure is set up like so dist/ [...see below] src/ global.d.ts index.ts otherfile.ts test/ examples/ To illustrate, the global.d.ts file contains typings ...

What is the best way to verify that I am receiving the 'jwt' token in my code?

Trying to understand the data held by the jwt token in my next.js app, but encountering an error message saying error: jwt must be provided. Here's the code snippet causing the issue: import { NextRequest } from "next/server" ...

I've tried using a ControlValueAccessor, but for some reason the value isn't getting passed to the form

Currently, I am experimenting with reactive forms in Angular, and I'm encountering difficulties with updating the form from custom components. As an example, take a look at the date-input component created using flatpickr in the linked Plunker demo. ...

"Can you share a method to extract the value from a TextField component in a React hook-based Material-

Currently, I am using Material-UI within a React project and have a component set up like this: const UserDetail = (props: ListDetailProps) => { const oldpassword = useRef<TextFieldProps>(null); const newpassword = useRef<TextFieldProps ...

Vue 3 app encountering error due to element having an 'any' type implicitly

I want to incorporate lucidev icons into my component, but I am fairly new to typescript. You can find the lucidev icons here: https://github.com/lucide-icons/lucide <template> <component :is="icon" /> </template> <script ...

Requiring Subclasses to Maintain Immutability

I have created a base class that contains various properties: class Component { readonly id: number readonly type: number } Now, I am looking to create some subclasses, such as: class HealthComponent extends Component { max_health: number, ...

Accessing the Parent Variable from a Function in JavaScript: A Guide

How can you properly retrieve the value of x? let x = 5 const f = (n:number) => { let x = "Welcome"; return x * n // Referring to the first x, not the second one } Also, what is the accurate technical term for this action? ...

Saving a JSON object to multiple JSON objects in TypeScript - The ultimate guide

Currently, I am receiving a JSON object named formDoc containing data from the backend. { "components": [ { "label": "Textfield1", "type": "textfield", "key": "textfield1", ...

The Firebase Cloud Function is failing to trigger on the onCreate event within the Firebase Realtime Database

I recently deployed a function to Firebase with the following code: import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; console.log('FILE LOADED'); const serviceAccount = require(' ...

Enhance the Nuxt 3 experience by expanding the functionality of the NuxtApp type with

When I include a plugin in my NuxtApp, it correctly identifies its type. https://i.stack.imgur.com/UFqZW.png However, when I attempt to use the plugin on a page, it only displays the type as "any." https://i.stack.imgur.com/hVSzA.png Is there a way for ...

Locate and return the index in Typescript

Can you get the index of an element in an array using the array.find() method in TypeScript? For example, I know I can use find to retrieve an object based on a property value, but is it possible to also retrieve the index of that object in the array? ...

How can I arrange a table in Angular by the value of a specific cell?

Here's the current layout of my table: Status Draft Pending Complete I'm looking for a way to sort these rows based on their values. The code snippet I've been using only allows sorting by clicking on the status header: onCh ...

Forwarding from a user interface element in Next.JS

I am currently working on a project utilizing Next.js 13, and I have encountered a situation where I need to invoke a client-side component from a server-side page. The specific component in question is the DeleteAddressAlertDialog which interacts with my ...

Issue with Angular and OIDC - user data is missing upon logging in

I am in the process of developing an application using Angular along with IdentityServer as the Single Sign-On (SSO) provider in ASP.NET. After successfully logging in and retrieving user information from the authentication server: User info The followin ...

retrieving class instances from a Firebase database

I have a new group called GroupA group A { value1: string; value2: string; total(): number { return value1 + value2; } } I want to store instances of GroupA in my database, but when I retrieve them, they are in Object format which does not a ...

Value that is constantly changing for ngModel

Is it possible to use a function as a value for ngModel in Angular? I need to be able to set the value for my input at a later point in time, so I want to check if the function exists before updating the model. However, the following code snippet is not ...