Creating a key/value pair type in Typescript that mirrors the shape of an object within an array: How to do it?

Here is the data that I have:

const pets = [
  {
    type: 'cat',
    name: 'penny'
  },
  {
    type: 'dog',
    name: 'Fido'
  },
  {
    type: 'fish',
    name: 'Nemo'
  }
];

In order for a pet to be considered valid, it must match one of these 3 exact shapes (i.e. both the key and the value must match). For example, this is a valid pet:

  {
    type: 'dog',
    name: 'Fido'
  }

However, these are not valid:

  {
    age: 'dog',
    name: 'Fido'
  },

and

  {
    type: 'dog',
    name: 'Sammy'
  },

and so on.

Since my actual object is much larger than this, I don't want to manually define this type. Is there a way to do it dynamically?

I attempted the following, but it did not work:

type Pet = {
  [key in keyof typeof pets]: typeof pets[key];
};

Answer №1

To improve the type inference in TypeScript, utilize the as const assertion on the pets array. This tells TypeScript to infer the most specific types possible rather than defaulting to { type: string, name: string }[], which is less informative.

const pets = [
  {
    type: 'cat',
    name: 'penny'
  },
  {
    type: 'dog',
    name: 'Fido'
  },
  {
    type: 'fish',
    name: 'Nemo'
  }
] as const;

With pets now typed as:

const pets: readonly [{
    readonly type: "cat";
    readonly name: "penny";
}, {
    readonly type: "dog";
    readonly name: "Fido";
}, {
    readonly type: "fish";
    readonly name: "Nemo";
}]

Observe the specificity of the objects with distinct strings.

The Pet type simplifies to:

type Pet = typeof pets[number]
/*
{
    readonly type: "cat";
    readonly name: "penny";
} | {
    readonly type: "dog";
    readonly name: "Fido";
} | {
    readonly type: "fish";
    readonly name: "Nemo";
}
*/

By indexing with number, the array type yields a union of all member types, representing each specific pet type.

Now, the following code demonstrates the expected behavior:

const fido: Pet = { // works
    type: 'dog',
    name: 'Fido'
}

const bad1: Pet = { // error due to Nemo being a fish
    type: 'cat', 
    name: 'Nemo'
}

const bad2: Pet = { // error as penny is a cat
    type: 'dog',
    name: 'penny'
}

Try it on the playground

Answer №2

Try using the Record function in conjunction with an enum to enforce specific types for certain values. It may not be the exact solution you're looking for, but it's worth a shot.

// Ensure only these keys are allowed in the Record type
enum Keys {
  TYPE = "type",
  NAME = "name",
}

// Define a Record type that can only have keys from the Keys enum and string values
type Pet = Record<Keys, string>;

const pets: Pet[] = [
  {
    type: "cat",
    name: "penny",
  },
  {
    type: "dog",
    name: "Fido",
  },
  {
    type: "fish",
    name: "Nemo",
  },
];

// Valid example
const pet: Pet = {
  type: "cat",
  name: "fido",
};

// Invalid example
const invalidPet: Pet = {
    age: 'dog',
    name: 'Fido'
};

Keep in mind that the Keys enum dictates which keys are allowed, while values are not restricted and can be any string.

If you also want to restrict the values (which may not be practical), you could create a separate enum for that purpose too.

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 assigning a JSON object as the resolve value and enabling autosuggestion when utilizing the promise function

Is there a way to make my promise function auto-suggest the resolved value if it's a JSON object, similar to how the axios NPM module does? Here is an example of how axios accomplishes this: axios.get("url.com") .then((res) => { Here, axios will ...

Encountering a delay in receiving server data causing errors in Angular 2

Within the service class constructor, I make an http call to the server to fetch product category data and store it in the productCategories array. However, when I try to retrieve the productCategories array in the ngInit() function of my component, I enco ...

Encountering a Typescript error while attempting to utilize mongoose functions

An example of a User interface is shown below: import {Document} from "mongoose"; export interface IUser extends Document{ email: string; password: string; strategy: string; userId: string; isValidPassword(password: string): ...

Tips on how to incorporate a .js file into my .tsx file

I ran into an issue with webpack displaying the following message: ERROR in ./app/app.tsx (4,25): error TS2307: Cannot find module './sample-data'. The imports in my code are as follows: import * as React from 'react'; import * ...

A data type labeled as 'undefined' needs to include a method called '[Symbol.iterator]()' which will then return an iterator

I've been working on converting my reducer from JavaScript to TypeScript, but I keep encountering a strange error that I can't seem to resolve. The issue arises when I attempt to use ellipsis for array deconstruction in the reducer [...state.mess ...

What causes the failure of $event binding when using RowGroup tables with PrimeNG?

I have successfully implemented RowGroup to store 3 different tables, which is working great. However, I am facing an issue with the onRowSelect function not functioning properly. When I click on any row of the RowGroup tables, nothing happens. On another ...

Poorly packaged library - Custom Angular library - Node Package Manager

Recently, I've been delving into the process of publishing a simple Angular library on NPM. Despite following various tutorials (like those found here, here, and here), I faced difficulties when attempting to use it in a test project. MY JOURNEY In s ...

Error: Virtual script not located; possibly absent <script lang="ts" / "allowJs": true / within the jsconfig.json.volar

https://i.sstatic.net/dFaVQ.png I noticed an error in my footer component in VueJs (TypeScript template) as depicted by the image showing blue squiggly lines. ...

Passing extra arguments to a callback function in Typescript

I'm trying to pass a parameter to a callback function. Below is the snippet of my function: let func = function(el, index){ if(el.id === myId) return index; } arr = [obj1, obj2, obj4, ...]; arr.filter(func); Is there a way to suc ...

Creating a read-only DIV using Angular - a step-by-step guide

Is there a simple way to make all clickable elements inside a div read only? For example, in the provided HTML code, these divs act like buttons and I want to disable them from being clicked. Any tips or shortcuts to achieve this? Thank you. #html < ...

Is there a way to include two objects in an Angular2 post request?

This piece of code is giving me trouble: On the client side (using Angular 2) saveConfig(configType: ConfigTypes, gasConfigModel: GasConfigModel): any { console.info("sending post request"); let headers = new Headers({ 'Content-Type& ...

Finding the appropriate method to access a template variable reference in a designated row of an Angular Material table (Angular 7)

Currently, I am working on implementing a more intricate version of a behavior inspired by Angular Material's tutorials. In my simplified example, an Angular Material table is populated with data from a string array. The first column contains input fi ...

Using Vue and TypeScript to define components

Whenever I attempt to install vue-class-component, vue-class-component, or vue-property-decorator, I encounter the following error message: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: <a ...

Storing file paths as string variables in Angular: a quick guide

I'm working with this line of code that selects all the files in a folder. <input type="file" id="filepicker" name="fileList" (change)="saveFolderLocation($event)" webkitdirectory/> My goal is to determin ...

What allows the type expression to be considered valid with a reduced amount of arguments?

Currently diving into Typescript and focusing on functions in this unit. Check out the following code snippet: type FunctionTypeForArrMap = (value: number, index: number, arr: number[]) => number function map (arr: number[], cb: FunctionTypeForArr ...

Set S3 Bucket Policy to include ELB Account

After utilizing the AWS console to create a bucket for access logging via the load balancer edit attributes screen, I am now in the process of transforming this action into CDK code using TypeScript. This allows me to automate the creation of new S3 bucket ...

Tips on pairing elements from a ngFor processed list with another list using ngIf

If we have a list such as the one shown below: elements = [ { id: 1, name: "one" }, { id: 3, name: "three" }, { id: 5, name: "five" }, { id: 6, name: "six" }, ]; lists = [ { id: 5, name: "five" }, { id: 9, ...

`Unable to upload spreadsheet file in xlsx format`

I'm currently working on implementing a feature to export data as xlsx files. I have been successful in exporting CSV and PDF formats, but encountered issues with the xlsx format due to dynamic imports. export const exportToXlsx = async ( gridElemen ...

What could be causing the cyclic dependency problem after upgrading to Angular 9?

I am experiencing an issue with a specific file containing the following code: import { Injectable } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { isNumber } from 'lodash'; import { Confir ...

Looking to execute multiple programs simultaneously within the prestart script in the package.json file, and bypass any exit error codes

I need to run yarn tsc and yarn lint during every yarn start to identify any code errors. This is how my scripts property is set up: "scripts": { "start": "expo start", "android": "expo start --android" ...