Working with an array of objects with varying shapes and validating them

I have dedicated quite a bit of time to this task and would greatly appreciate some assistance. I am in need of a component (a function) that can accept an array of objects while also validating the properties of these objects.

Here are the interfaces and data structures:

interface ObjectWithId {
  id: string | number;
}

interface TableMeta<T extends ObjectWithId, K extends PropertyKey = keyof T> {
  data: T[];
  searchKey: K;
  onClick?: (item: T) => void;
}

interface Vegetable {
  id: number,
  label: string,
}

interface Fruit {
  id: number, 
  name: string,
}

const vegetableMeta: TableMeta<Vegetable> = {
  data: [],
  searchKey: 'label', // only allows 'label' or 'id' 👍
}

const fruitMeta: TableMeta<Fruit> = {
  data: [],
  searchKey: 'name', // only allows 'name' or 'id' 👍
  onClick: (item) => {item.id} // ✔️ has correct item type <---------------
}

const metas = [vegetableMeta, fruitMeta];

Now, let's look at a component (which is essentially a function):

const metaParser = (metas: TableMeta<{id: number | string}, PropertyKey>[]) => {
  const id = metas[0].data[0].id; // should be `number | string`
}

metaParser(metas); // ❌ Type 'ObjectWithId' is not assignable to type 'Vegetable'

The specific structure of objects within the array cannot be predetermined

Any suggestions on how we can make this work?

TS Playground

Answer №1

To enhance the functionality, one strategy is to unveil the callback type and broaden its applicability when managing a group of items.

interface ObjectWithId {
  id: string | number;
}

interface TableMeta<
  T extends ObjectWithId,
  K extends PropertyKey = keyof T,
  F extends (item: never) => void = (item: T) => void, // <--- reveal the type
> {
  data: T[];
  searchKey: K;
  onClick?: F;
}

interface Vegetable {
  id: number,
  label: string,
}

interface Fruit {
  id: number, 
  name: string,
}

const vegetableMeta: TableMeta<Vegetable> = {
  data: [],
  searchKey: 'label',
}

const fruitMeta: TableMeta<Fruit> = {
  data: [],
  searchKey: 'name',
  onClick: (item) => {item.id}
}

const metas = [vegetableMeta, fruitMeta];

                                                broaden the scope ---⌄
const metaParser = (metas: TableMeta<ObjectWithId, PropertyKey, (item: never) => void>[]) => {
  const id = metas[0].data[0].id;
}

metaParser(metas);

Answer №2

UPDATE THREE

type Base = { id: number | string } & { [p: string]: unknown }

interface Data<T extends Base> {
  data: Array<T>;
}

interface SearchKey<T extends Base> {
  searchKey: Exclude<keyof T, 'id'>;
}

type TableMeta<T extends Base> = Data<T> & SearchKey<T>

/**
 * It seems this additional setup is necessary for accurate type inference
 */
const builder = <T extends Base, K extends Exclude<keyof T, 'id'>>(searchKey: K, data: Array<T>): TableMeta<T> => ({ data, searchKey });


const metas = [
  builder('name', [{ id: 2, name: 2 }]),
  builder('key', [{ id: '2', key: 'John Doe' }]),
  // builder('hello', [{ id: 3, age: 42 }]) //expected error
];

const metaParser = <T extends TableMeta<Base>>(metas: Array<T>) => {
  const result = metas // T[]
  const result2 = metas[0].searchKey// string | number
  const result3 = metas[0].data[0].id// string | number
  return metas
}


const result = metaParser(metas); // ok

Here you can discover more insights on typing callbacks. Implementing this without the helper builder function may pose a challenge.

You might want to explore this solution, but it still requires the use of extra functions.

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

Access an array to filter by specific key-value pairs

How can I efficiently filter an array using key and value pairs from within nested arrays? I am in need of a method to filter the elements of an array based on specific key-value pairs nested within another array. The key will always contain boolean value ...

To emphasize the chosen item following a component's update

SCENARIO: A component named list is used to display a list of all customers. The conditions are as follows: 1) By default, the first list-item (e.g. customer 1) is selected and emitted to another component called display. 2) When any other list-item (i.e ...

Angular2 - how can I effectively organize the logic between my components and services?

Within my current project setup, I have the following structure implemented: I have a Component that interacts with a Service Class which in turn calls an external API. The specific logic that I need to implement is related solely to the user interface. ...

Can classes from an external package be imported into an Angular library within an Angular app using Openlayers?

I am currently developing an Angular library that configures an OpenLayers map as an Angular component. The component is functioning properly, but I need to access classes and constants from a package imported by the library, specifically OpenLayers. To w ...

Mapping strings bidirectionally in Typescript

I am currently working on a two-way string mapping implementation; const map = {} as MyMap; // need the correct type here const numbers = "0123456789abcdef" as const; const chars = "ghijklmnopqrstuv" as const; for (let i = 0; i < n ...

What is the process for creating a Deep Copy of an object in Angular?

I have a specific entity class defined as follows: export class Contact { id: number; firstName: string; lastName: string; constructor(id?, firstName?, lastName?) { this.id = id; this.firstName = firstName; this.lastName = lastName; ...

Issue with passing parameters to function when calling NodeJS Mocha

I have the following function: export function ensurePathFormat(filePath: string, test = false) { console.log(test); if (!filePath || filePath === '') { if (test) { throw new Error('Invalid or empty path provided'); } ...

Generating data types based on the output of functions

I'm currently working on optimizing my typescript react code by reducing repetition. I'm curious to know if there's a way to generate a type based on the return type of a known function? For example: const mapStateToProps = (state: StoreSt ...

Angular 10 Reactive Form - Controlling character limit in user input field

I'm currently developing an Angular 10 reactive form and I am looking for a way to restrict the maximum number of characters that a user can input into a specific field. Using the maxLength Validator doesn't prevent users from entering more chara ...

Discovering Typescript: Inferring the type of a union containing specific strings

I am working with a specific type called IPermissionAction which includes options like 'update', 'delete', 'create', and 'read'. type IPermissionAction = 'update' | 'delete' | 'create' | ...

Encountered a React TypeScript issue stating that the type '{ ... }' cannot be assigned to the type 'IntrinsicAttributes & IntrinsicClassAttributes<...>'

Embarking on a new journey with a react typescript project, I encountered this puzzling error: Failed to compile. /Users/simon/Code/web/react-news-col/src/MainNewsFeed.tsx TypeScript error in /Users/simon/Code/web/react-news-col/src/MainNewsFeed.tsx(27,35 ...

Array that accepts the type of the first element as its generic parameter

There was a type challenge The task was to create a generic function called First that takes an array T and returns the type of its first element. type arr1 = ["a", "b", "c"]; type arr2 = [3, 2, 1]; type head1 = First<arr1>; // expected: 'a& ...

What steps can be taken to avoid an abundance of JS event handlers in React?

Issue A problem arises when an application needs to determine the inner size of the window. The recommended React pattern involves registering an event listener using a one-time effect hook. Despite appearing to add the event listener only once, multiple ...

Building a TTL based schema in NestJs with MongooseIn this guide, we will explore

In my NestJs(TypeScript) project, I am attempting to create a self-destructing schema using the mangoose and @nestjs/mongoose libraries. Unfortunately, I have been unable to find a clear way to implement this feature. While I know how to do it in an expres ...

(React Native - Expo) The hook array fails to include the most recently selected object

When I attempt to add objects to a hook within a component, it seems to be functioning correctly. However, there is an issue where the last selected object is consistently missing from the updated hook array. This behavior also occurs when removing an obje ...

Is it possible to utilize an XML format for translation files instead of JSON in React Native?

I'm in the process of creating a react native application using the react i18next library. For translations, I've utilized XML format in android for native development. In react native, is it possible to use XML format for translation files inste ...

Having trouble accessing the useState hook in React context value with TypeScript

export const TeamMemberContext = createContext<TeamMembersList[] | null>(null); export const TeamMemberProvider = ({ children }) => { const [teamMemberList, setTeamMemberList] = useState<TeamMembersList[] | null>(null); useEffect(( ...

Encountering the error message "TypeError: Cannot access property 'Token' of undefined" while compiling fm.liveswitch

The fm.liveswitch JavaScript Software Development Kit (SDK) is designed for use with both clients and your own backend "app server". It functions smoothly in the frontend thanks to webpack and babel. However, the same import statement: import liveswitch fr ...

The TypeScript error occurs when trying to set the state of a component: The argument 'X' cannot be assigned to the parameter of type '() => void'

When I attempt to call setState, I encounter a TypeScript error. Here is the code snippet causing the issue: updateRequests(requests: any, cb:Function|null = null) { this.setState( { requests: { ...this.state.requests, ...

Utilizing Window function for local variable assignment

Currently, I am facing a challenge in my Angular2 service where I am attempting to integrate the LinkedIN javascript SDK provided by script linkedin. The functionality is working as expected for retrieving data from LinkedIN, however, I am encountering an ...