Typescript: Utilizing a generic array with varying arguments

Imagine a scenario where a function is called in the following manner:

func([
   {object: object1, key: someKeyOfObject1},
   {object: object2, key: someKeyOfObject2}
])

This function works with an array. The requirement is to ensure that the key field contains a valid key of the corresponding object specified in the object property. It's important to note that each object may have a different structure.

Defining such a type for a single object is relatively straightforward:

type Type<T> = { obj: T, key: keyof T }

The challenge lies in creating an array that enforces this rule for every element it contains. Using Type<any>[] would remove all type information.

Answer №1

It presents a challenge to restrict this on the function side. Finding a universal solution seems unlikely.

Non-conventional approach: using function overload

interface Data<TObject> {
  object: TObject
  key: keyof TObject
}

function manipulate<T1, T2, T3, T4>(items: [Data<T1>, Data<T2>, Data<T3>, Data<T4>]): void
function manipulate<T1, T2, T3>(items: [Data<T1>, Data<T2>, Data<T3>]): void
function manipulate<T1, T2>(items: [Data<T1>, Data<T2>]): void
function manipulate<T1>(items: Data<T1>[]): void {

}

manipulate([
  { object: { a: '1' }, key: 'a' },
  { object: { b: '1' }, key: 'b' },
  { object: { c: '1' }, key: 'a' }, // not allowed
])

Alternative method: ensuring types on the calling end Essentially, you depend on a utility function. There is still room for error here that may go unnoticed by the compiler (refer to the last item in the example)

interface Data<TObject extends object> {
  object: TObject
  key: keyof TObject
}

function manipulate(data: Data<any>[]) {

}

function createData<T extends object>(object: T, key: keyof T): Data<T> {
  return {
    object,
    key
  }
}

manipulate([
  createData({ a: 1 }, 'a'),
  createData({ b: 2 }, 'f'), // not allowed
  { object: { b: 2 }, key: 'f' }, // allowed
])

Method 3: forming a processor entity with a generic add function

interface Data<TObject> {
  object: TObject
  key: keyof TObject
}

function establishProcessingArray() {
  const array: Data<any>[] = []

  return {
    add<T>(item: Data<T>) {
      array.push(item)

      return this
    },
    result() {
      // process the array and provide the outcome
    }
  }
}

const result = establishProcessingArray()
  .add({ object: { a: '1' }, key: 'a' })
  .add({ object: { b: '1' }, key: 'b' })
  .add({ object: { c: '1' }, key: 'a' }) // not allowed
  .result()

Answer №2

Executing this task can be achieved without the need for additional functions, albeit with a slight increase in code complexity:

type Entity<Obj, Key> = {
    object: Obj,
    key: Key
}

type IsValid<T extends Entity<any, any>[]> = 
    /**
     * Inference of each element in the array
     */
    T[number] extends infer Elem 
    /**
     * Verification if every element in the array extends Entity
     */
    ? Elem extends Entity<any, any> 
    /**
     * Checking if the `key` property matches any of the properties within Elem['object']
     * 1) Return true if [key] is one of the object's properties
     * 2) If at least one element does not meet the requirements, return false | true,
     * since some elements are satisfactory
     */
    ? keyof Elem['object'] extends Elem['key'] 
    ? true 
    : false 
    : false 
    : false;

// credits to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

// credits to https://stackoverflow.com/users/125734/titian-cernicova-dragomir
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type Validator<T extends boolean> =
    /**
     * If IsValid returns false | true (boolean), it indicates an error
     * otherwise - everything is fine
     */
    IsUnion<T> extends true ?
    ['Dear developer, please correct something']
    /**
     * Using an empty array here, as 
     * (...flag:[])=>any evaluates to a function without arguments
     */
    : []

const foo = <
    Value extends Record<PropertyKey, string>,
    Key extends keyof Value,
    Data extends Entity<Value, Key>[],
>(a: [...Data], ...flag: [...Validator<IsValid<[...Data]>>]) => a

/**
 * All good
 */
foo([{
    object: { name: 'John' }, key: 'name'
},
{
    object: { surname: 'John' }, key: 'surname'
}])

/**
 * Error occurred
 */
foo([{
    object: { name: 'John' }, key: 'name'
},
{
    object: { surname: 'John' }, key: 'name'
}])

This solution encompasses two main components:

Part 1

We must use variadic tuple types to infer each element of the array using the generic Data. More details on this approach are covered in my article here.

Part 2

We need to ensure that each element meets the specified criteria by utilizing type utilities such as Validator and IsValid. For further insights into this technique, you can explore my blog posts here and here

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

React Material UI Select component is failing to recognize scrolling event

Having some difficulty understanding how to detect a scroll event with a Select component using Material-UI. The Select has MenuProps={...}, and I want to listen for the scroll event inside it. I've tried putting onScroll within MenuProps={...}, but ...

Retrieve recently appended DOM elements following the invocation of createComponent on a ViewContainerRef

I have a current function in my code that dynamically creates components and then generates a table of contents once the components are added to the DOM. This service retrieves all h3 elements from the DOM to include in the table of contents: generateDy ...

Having trouble compiling my Angular application with success

I am working with a file named mock-values.ts. In this file, I have defined the following constants: export const TIMES: Time[] = [ { i: '8:00', v: '8' }, { i: '8:30', v: '8:30' }, { i: '9:00', v: &apo ...

Step-by-step guide on incorporating an external JavaScript library into an Ionic 3 TypeScript project

As part of a project, I am tasked with creating a custom thermostat app. While I initially wanted to use Ionic for this task, I encountered some difficulty in integrating the provided API into my project. The API.js file contains all the necessary function ...

Comparing the functions of useMemo and the combination of useEffect with useState

Is there a benefit in utilizing the useMemo hook instead of using a combination of useEffect and useState for a complex function call? Here are two custom hooks that seem to function similarly, with the only difference being that useMemo initially returns ...

Is Intellisense within HTML not available in SvelteKit when using TypeScript?

Having trouble with intellisense inside HTML for a simple page component. Also, renaming properties breaks the code instead of updating references. Typescript version: 4.8.4 Below is the code snippet: <script lang="ts"> import type { Bl ...

Pass a React component as a required prop in Typescript when certain props are necessary

I am currently working on a project where I need to create a custom TreeView component using React and Typescript. My goal is to have the ability to inject a template for each TreeNode in order to render them dynamically. My main challenge right now is fi ...

Vue 4 and TypeScript: Dealing with the error message 'No overload matches this call'

In my Vue-Router 4 setup, I am trying to combine multiple file.ts files with the main vue-router (index.ts) using TypeScript. However, it throws an error that says "TS2769: No overload matches this call. Overload 1 of 2, '(...items: ConcatArray[]): ne ...

Troubleshooting Typescript app compilation problem in a Docker environment

I am encountering a challenge while trying to build my typescript Express app using Docker. Surprisingly, the build works perfectly fine outside of Docker! Below is the content of my Dockerfile: FROM node:14-slim WORKDIR /app COPY package.json ./ COPY yarn ...

The toggle-input component I implemented in React is not providing the desired level of accessibility

Having an accessibility issue with a toggle input while using VoiceOver on a Mac. The problem is that when I turn the toggle off, VoiceOver says it's on, and vice versa. How can I fix this so that VoiceOver accurately states whether the toggle is on o ...

Issue with vue-class-component: encountering TS2339 error when trying to call a method within

My vuejs application is being built using vue-cli-service. After a successful build, I encountered TS2339 errors in my webstorm IDE: Test.vue: <template> <div>{{method()}}</div> </template> <script lang="ts"> impor ...

Does the class effectively implement the interface even if the method of a member variable has undefined arguments?

Let's take a closer look at my code, which lacks proper descriptions. Here is the interface: interface IModel<T = any> { effects: { [key: string]: (getState: () => T) => void; }; } interface IState { name: string; age: numbe ...

Guide to making a Typescript type guard for a ReactElement type

I'm currently working with three TypeScript type guards: const verifyTeaserOne = (teaser: Teaser): teaser is TeaserOneType => typeof teaser === 'object' && teaser.type.includes('One'); const validateTeaserTwo = ( ...

Issue with Typescript Application not navigating into the node_modules directory

After attempting to load the app from the root directory of our server, it became clear that this was not a practical solution due to the way our application uses pretty URLs. For instance, trying to access a page with a URL like http://www.website.com/mod ...

The Threejs Raycaster detects collisions with objects even when the ray is just grazing the surface

Within my Vue component, I have integrated a threejs scene and I am facing an issue with selecting objects using the mouse. I am currently using the OnPointerDown event and raycaster to locate objects under the mouse pointer. However, it seems like the ray ...

Developing modules with Typescript

I am eager to develop custom modules for our library so that we can simply call import {Api, Map} from "ourlibrary" At the moment, I am using the following setup. import {Api} from "../../Library/Api"; import {MapPage} from "../map/map"; Could someone g ...

Passing properties from the parent component to the child component in Vue3JS using TypeScript

Today marks my inaugural experience with VueJS, as we delve into a class project utilizing TypeScript. The task at hand is to transfer the attributes of the tabsData variable from the parent component (the view) to the child (the view component). Allow me ...

Utilizing AMD Modules and TypeScript to Load Bootstrap

I am attempting to incorporate Bootstrap into my project using RequireJS alongside typescript AMD modules. Currently, my requireJS configuration looks like this: require.config({ shim: { bootstrap: { deps: ["jquery"] } }, paths: { ...

Ways to retrieve data beyond the constructor

Here is the code snippet from my component.ts: export class OrganizationsComponent { public organizations; constructor(public access: OrganizationService) { this.access.getOrganizations().subscribe((data => { this.organizations = data; ...

Updating the parent navigation bar after a successful login in a child component in Angular4

In my Angular4 project, I have set up a login page. Within the parent app.component file, I have included a navigation bar with login and signup buttons. Upon successful login, the login and signup buttons should be hidden, and the username should appear i ...