Determining the type of index to use for an interface

Imagine having an interface called Animal, with some general properties, and then either be a cat or a dog with corresponding properties.

interface Dog {
    dog: { sound: string; }
}

interface Cat {
    cat: { lives: number; }
}

type CatOrDog = Cat | Dog;

interface Animal {
    weight: number;
    // index type of CatOrDog
}

I came up with this idea

interface Animal {
   weight: number;
   [K in keyof CatOrDog]: CatOrDog[K];
}

However, TypeScript throws errors when trying to use something other than [K:string]: type

The goal is

// Success
const dog = <Animal> {
    weight: 5,
    dog: {sound: "woof" }
}

// Error, lives doesn't exist on Dog
const errorAnimal = <Animal> {
    weight: 5,
    dog: {sound: "woof" },
    cat: { lives: 9 }
}

Can more index types be added if needed?

Answer №1

Groups such as Lion | Tiger are considered to be inclusive, indicating that something falls under the category of Lion | Tiger if it is either a Lion, a Tiger, or even both. TypeScript lacks a universal exclusive union operator. If your groups have common properties with distinct values, you can utilize discriminated unions similar to what @MateuszKocz recommends. Alternatively, you could create your own Xor type function for objects:

type ProhibitKeys<K extends keyof any> = { [P in K]?: never }

type Xor<T, U> = (T & ProhibitKeys<Exclude<keyof U, keyof T>>) |
  (U & ProhibitKeys<Exclude<keyof T, keyof U>>);

In this case, you can define Creature as the exclusive union of Lion and Tiger, combined with additional properties shared by all Creatures:

type Creature = Xor<Lion, Tiger> & { speed: number };

With this setup, you can achieve the desired functionality (using type annotations instead of assertions for clarity):

// Successful assignment
const lion: Creature = {
  speed: 10,
  lion: { roar: "Roarrr" }
}

// Error, {stripes: number} cannot be assigned to undefined
const errorCreature: Creature = {
  speed: 8,
  tiger: { stripes: 7 },
  lion: { mane: true }
}

I hope this explanation proves helpful; best of luck!

Answer №2

If you're open to making some adjustments to your code, utilizing tagged unions might just be the solution you've been searching for.

interface CommonAnimal {
  weight: number
}

interface Dog extends CommonAnimal {
  // Here's where it gets crucial. The `type` property acts as a tag for TypeScript to identify this type.
  type: 'dog'
  sound: string
}

interface Cat extends CommonAnimal {
  type: 'cat'
  lives: number
}

type Animal = Dog | Cat

const dog: Animal = {
  type: 'dog',
  weight: 10,
  sound: 'woof'
}

const cat: Animal = {
  type: 'cat',
  weight: 5,
  lives: 9
}

const robot: Animal = {
  type: 'robot' // This will trigger an error
}

This approach allows you to maintain values at a single level without nesting, all while meeting TypeScript's requirements for recognizing types.

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

Utilize Protractor Selenium to extract content from a popup window

Having trouble capturing the text from a popup using Protractor with getText? The HTML structure can be found here. This popup only appears for a few seconds before disappearing. Can anyone assist me in retrieving the text from this popup? To retrieve the ...

Having trouble with the jQuery function not working as expected? Can't seem to identify any errors in the code?

I'm attempting to capture the essence of moving clouds from this beautiful theme: (I purchased it on themeforest, but it's originally designed for tumblr) Now, I want to incorporate it into my wordpress website here: The code used to be under ...

Retrieve JSON data from PHP using D3.request

Looking to extract data from an SQL database using PHP and then convert it into JSON format with the "echo json_encode($array);" function. I have a requirement to create a graph using D3.js, which means I need to transfer this JSON data from PHP. Can anyo ...

Learn how to use canvas and JavaScript to draw lines that display x and y coordinates on top of the mouse pointer at the same time

Implement a functionality in which lines are drawn while the mouse button is held down and simultaneously display x & y coordinates on top of the mouse pointer during mouse movement using canvas and JavaScript. The issue arises when attempting to draw lin ...

What is the best way to convert Observable<Observable<{...}>[ ]> to Observable<{...}[ ]>?

I am currently working on merging two observable objects into a single observable to access data from both. These observables represent documents from separate collections in a NoSQL database, specifically a Cloud Firestore database. Both collections have ...

Problem encountered when attempting to return data received from database queries executed within a loop

Having an issue with making multiple MongoDB queries in a loop and trying to send all the results as one data array. However, simply using 'return' to send the data is resulting in 'undefined' and not waiting for the results of all DB r ...

What is causing certain code to be unable to iterate over values in a map in TypeScript?

Exploring various TypeScript idioms showcased in the responses to this Stack Overflow post (Iterating over Typescript Map) on Codepen. Below is my code snippet. class KeyType { style: number; constructor(style) { this.style = style; }; } fu ...

Failed to decipher an ID token from firebase

I'm feeling extremely frustrated and in need of assistance. My goal is to authenticate a user using Google authentication so they can log in or sign up. Everything worked perfectly during development on localhost, but once I hosted my app, it stopped ...

Is there a way to safeguard against accidental modifications to MatTab without prior authorization?

I need to delay the changing of the MatTab until a confirmation is provided. I am using MatDialog for this confirmation. The problem is that the tab switches before the user clicks "Yes" on the confirmation dialog. For instance, when I try to switch from ...

Desktop display issue: Fontawesome icon not appearing

Having trouble getting the fontawesome icon to display properly on my website. It appears in inspect mode, but not on the actual site itself. Any suggestions on how to fix this issue? import React, { Fragment, useState} from "react"; import { Na ...

Is the alert failing to appear during the onbeforeunload event across all web browsers?

Check out the following code snippet that is functional. window.onbeforeunload = function() { someAjaxCall(); } This code block, however, does not perform as expected: window.onbeforeunload = function() { someAjaxCall(); alert("Success !!"); ...

Sequelize encountered an error: getaddrinfo ENOTFOUND (even though the address is correct)

I've encountered some Sequelize errors while attempting to deploy a site I developed using Angular and Express/Sequelize. Here's the issue: Everything works perfectly on my local WAMP server, connecting to the local database without any problems ...

What kind of type is recommended to use when working with async dispatch in coding?

For my TypeScript and React project, I am currently working on an action file called loginAction.tsx. In this file, there is a specific line of code that handles the login functionality: export const login = (obj) => async dispatch => { dispatch( ...

Leveraging HTTP/2 in conjunction with angularJS

As I was exploring ways to improve the performance of my web application, I came across HTTP/2. After learning about its features that can enhance website speed, I decided to implement it. Upon upgrading my browser to the latest version to enable HTTP/2 s ...

Using node.js to send a response with response.writeHead on the http module

While working on my own custom http module, I stumbled upon a few confusing points while studying the official node.js http module api: When a user utilizes the response.writeHead(statusCode, [reasonPhrase], [headers]) function, are the headers suppose ...

The initial Get request does not receive data upon first attempt

In the process of developing an Angular project, I am faced with the task of retrieving data from my backend by making requests to an API. However, before the backend can fetch the required data, certain parameters must be sent through a post request. Once ...

Having trouble retrieving a value from the img.onload event handler. A 'boolean' type error is being thrown, indicating it cannot be assigned to type '(this: GlobalEventHandlers, ev: Event) => any'

In my Angular application, I have implemented a method that verifies the size and dimensions of an image file and returns either false or true based on the validation result. Below is the code snippet for this function: checkFileValidity(file: any, multipl ...

Is there a similar feature to RxJs version 4's ofArrayChanges in RxJs version 5?

Currently utilizing Angular2 and attempting to monitor changes in an array. The issue lies with only having RxJs5 available, which appears to lack this specific functionality. ...

"Unlocking the potential of AngularJS: A guide to accessing multiple controllers

I am trying to set a variable in one instance of a controller and read it in another. The variable I need to set is within the object vm (so $scope cannot be used). This is the code for the controller: app.controller("AppController", function(){ var ...

Error: Unable to access the 'nom_gr' property of null - encountered in Chrome

<ion-col col-9 class="sildes"> <ion-slides slidesPerView="{{nbPerPage}}" spaceBetween="5"> <ion-slide *ngFor="let slide of lesClassrooms; let i = index" (click)="saveCurrentSlide(i)"> ...