Detecting missing identifiers or keys in arrays of objects using Typescript

Let's consider a basic scenario where we have a simple type that represents a key of a Thing:

type ThingKey = "a" | "b" | "c"

We want to create an entry with some metadata for each value in order to iterate over it or use it as a map:

interface Thing {
  key: ThingKey
  stuff: string
  moreStuff: number
}

const things: Thing[] = [
  {
    key: "a",
    stuff: "AAA",
    moreStuff: 42,
  },
  {
    key: "b",
    stuff: "BBB",
    moreStuff: 43,
  },
  {
    key: "c",
    stuff: "CCC",
    moreStuff: 44,
  },
]

const thingByKey = _.keyBy(things, thing => thing.key);

Everything seems fine so far, but TypeScript doesn't ensure that our array contains entries for all possible values of ThingKey. We need to guarantee that there is exactly one entry in things for each value in the ThingKey type. How can we accomplish this?


An alternative approach would be to define a map initially and then convert it into an array. However, this method involves mentioning the same key twice, leading to data redundancy and the possibility of creating inconsistencies between the keys within the object and the Thing itself.

export const alternativeThingByKey: Record<ThingKey, Thing> = {
  a: {
    key: "a",
    stuff: "AAA",
    moreStuff: 42,
  },
  b: {
    key: "b",
    stuff: "BBB",
    moreStuff: 43,
  },
  c: {
    key: "b", // <-- whoops
    stuff: "CCC",
    moreStuff: 44,
  },
}

export const alternativeThings = Object.values(alternativeThingByKey)

Answer №1

In TypeScript, converting a union type to a tuple type is not possible due to the differences in their nature - tuples are ordered while unions are not. However, it is possible to convert one tuple to another using mapped tuples:

type ThingKeyValues = ["a", "b", "c"];

interface Thing<K> {
    key: K
    stuff: string
    moreStuff: number
}

type Thingify<T> = { [P in keyof T]:  Thing<T[P]> }

type Things = Thingify<ThingKeyValues> & {length: ThingKeyValues['length']}

Although TypeScript's support for tuples is still evolving, we can make use of a redundant generic and introduce a 'length' union for successful typing:

// successful checks

const goodThings: Things =  [
  {key: "a", stuff: "AAA", moreStuff: 42,},
  {key: "b", stuff: "BBB", moreStuff: 43,},
  {key: "c", stuff: "CCC", moreStuff: 44,},
]

// unsuccessful check

const badThings1: Things =  [
  {key: "a", stuff: "AAA", moreStuff: 42,},
  {key: "X", stuff: "BBB", moreStuff: 43,},
  {key: "c", stuff: "CCC", moreStuff: 44,},
]

// unsuccessful check

const badThings2: Things =  [
  {key: "a", stuff: "AAA", moreStuff: 42,},
  {key: "b", stuff: "BBB", moreStuff: 43,},
  {key: "c", stuff: "CCC", moreStuff: 44,},
  {key: "MORE", stuff: "CCC", moreStuff: 44,},
]

PG

Answer №2

To ensure that all components of a union string literal type are defined, the most effective approach is to utilize the Record<ThingKey, Thing> solution as suggested.

Alternatively, if the ThingKey is altered slightly to become a tuple type, it is possible to define Things using a mapped tuple type based on ThingKey, as illustrated below:

type ThingKey = ["a", "b", "c"];

interface Thing<K> {
  key: K;
  stuff: string;
  moreStuff: number;
}

// generic type parameter required for mapped tuple type
type Things<T extends any[]> = { [K in keyof T]: Thing<T[K]> };

const t: Things<ThingKey> = [
  {
    key: "a",
    stuff: "AAA",
    moreStuff: 42
  },
  {
    key: "b",
    stuff: "BBB",
    moreStuff: 43
  },
  {
    key: "c",
    stuff: "CCC",
    moreStuff: 44
  }
];

This approach enforces strict adherence to the ordering within the ThingKey when utilized in the mapped type.

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

Are there any methods within Angular 2 to perform Angular binding within a string?

When creating an HTML template with routing, such as shown below: <ul class="sb-sub-menu"> <li> <a [routerLink]="['clientadd']">Client Add</a> </li> </ul> It functions as expected. However, w ...

What is the most effective method for sorting through vast amounts of data with Javascript, React, and Redux?

Currently, I am working with a minimum JSON data set of 90k [...] and utilizing the .filter method. Everything is functioning correctly without any issues, however, from a performance standpoint, I am curious about potential improvements. Any suggestions o ...

Displaying data in a tabular format using an array

Almost finished with this, but encountering the following issue: <table border="1" cellpadding="2"> <thead> <tr> <th>Item Code</th> <th>Description </th> ...

What is the best way to tally the elements of a JavaScript array and produce a desired output format

I have an array that looks like this: ["5763.34", "5500.00", "5541.67", "5541.67"] I am looking to count similar values in the array and produce an output as follows: (1 * 5763.34) + (1 * 5500.00) + (2 * 5541.67) Does anyone have any ideas on how to ac ...

The deferred type in Typescript that extends T is unknown at this time

While reviewing code in a library, I came across the line unknown extends CustomTypes[K]. My understanding is that this is a deferred type where unknown can always be assigned to CustomTypes[K]. However, I am unsure how this type is utilized and in what ...

Mastering the art of crafting an Angular Domain Model accurately

When creating a domain model, is it best to define and set the properties inside the constructor for efficiency? In my attempt below, I have defined the properties outside the constructor. Should I be setting them only inside the constructor to reduce the ...

Creating a Typescript interface that includes keys from another interface

interface A{ a: string; b: string; c: string; // potentially more properties } interface B{ [K in keyof A]: Boolean; } What could be the issue with this code? My goal is to generate a similar structure programmatically: interface B{ ...

Retrieve a single record in Angular/Typescript and extract its ID value

There is data stored in a variable that is displayed in the Chrome console like this: 0: @attributes: actPer: "1", id: "19" 1: @attributes: actPer: "1" id: "17" etc To filter this data, the following code was used: myvar = this.obj.listR ...

Retrieving row and column values from an 81-element indexed array

I am working with an indexed JavaScript array that contains exactly 81 elements. These elements will form a 9x9 grid, similar to a Sudoku puzzle. Right now, I am trying to figure out the most efficient way to extract row and column values from this array. ...

The dreaded glitch in Angular's setInterval function

My goal is to implement polling for a single page and disable it when the user navigates away from that page. However, I encountered an error during the build process when I attempted to set up the polling interval using setInterval: error TS2362: The lef ...

transforming vector references and arrays

After tirelessly searching for an answer, I am resorting to reposting my question. I am attempting to implement Heap's algorithm in cpp but cannot seem to figure out why a certain line of code is not working. I was under the impression that when modif ...

Combine three arrays by a predetermined index value to generate a consolidated array

I have three arrays that share the same first index, and I am looking to combine them into one array based on the first index element. Input: [[1, 'A'], [2, 'B'], [3, 'C']]; [[1, 'D'], [2, 'E'], [3, ' ...

Struggling to configure typescript and babel to work together smoothly within webpack

Currently, I am in the process of converting a project to TypeScript, and it is required to utilize decorators on both classes and function parameters: function(@inject(TYPES.thing) thingy){...} I believe I am very close to completing this task, just mis ...

Tips for accurately implementing the onHoverIn TS type in the React Native Web Pressable component

I'm working with React Native Web and Typescript, and I want to integrate the React Native Web Pressable component into my project. However, I encountered an issue where VSCode is showing errors for React Native Web prop types like onHoverIn. The pro ...

The optimal way to rearrange elements within an array following a comparison with another array

Consider two arrays, 'a1' and 'a2', that have the same elements but in different orders. What is the most efficient way to compare these arrays and determine the minimum number of swaps required to rearrange 'a1' to match &ap ...

The number in Typescript should fall between 0 and 1, inclusive

Is there a method in Typescript that guarantees the value of a number will be less than or greater than a certain threshold? Currently, it permits the specification of a range of values, but I'm unsure about comparison. This is similar to what I have ...

Generics in Classes: Unintelligible Error

For a demonstration, you can check out the implementation in this codesanbox. The class "Operation.ts" contains all the details. The purpose of the "Operation" class is to manage operations performed on objects such as rectangles. Each operation type ("mo ...

``There are problems with parsing JSON data due to the error message indicating the presence of unexpected

I am encountering an issue with displaying values from objects stored as string[] in appwriteDB. When trying to use *ngFor to iterate through the data, I faced difficulties. Despite attempting to convert the orderItems using JSON.parse(), the process faile ...

Error encountered in Angular 2 with RXJS Observable: Unable to call function catch() on this.http.get(...).map(...) due to TypeError

Everything was running smoothly with my Service until today, when I encountered the following error: TypeError: this.http.get(...).map(...).catch is not a function. Upon debugging the code, it crashes at the catch method. import { Test } from "./home.c ...

Guide to showcasing multiple paths of Firebase data on a single Angular page

I am working with a basic database structure that includes information about groups, events, and users. Here is an example: { "groups": { "123": { "name": "developers", "users": { "1": true }, "users_count": 1 } ...