The specified property is not found within type T

Struggling to make a function more generalized here. The error message stating

[E] Property 'displayName' does not exist on type 'T[{ [k in keyof T]: T[k] extends Base ? k : never; }[keyof T]]'
has got me puzzled.

interface Base {
  id: string;
  displayName: string;
}

interface Station extends Base {
  district: Base;
  city: Base;
}

type Item<T> = { [ k in keyof T ]: T[k] extends Base ? k : never }[keyof T] | 'self';

type Result = { displayName: string };

const f = <T extends Base>(items: Item<T>[], obj: T): Result[] => {
  return items
    .map(item => {
      if (item === 'self') {
        return { displayName: obj.displayName };
      }
      return {
        displayName: obj[item].displayName, // displayName does not exist
      };
    });
};

const s: Station = {
  id: '1234',
  displayName: 'Station 1',
  district: { displayName: 'Test', id: '1' },
  city: { displayName: 'Testcity', id: '1' },
};

const r = f(['city', 'self'], s); // expected: [ { displayName: 'TestCity' }, { displayName: 'Station 1' } ]

Answer №1

Function f utilizes the type T which refers to Base. It follows that obj is of type Base, restricting the use of keys for indexing to only id or displayName.

It appears that what you truly require is for obj to be a type representing Station, rather than Base:

interface Base {
  id: string;
  displayName: string;
}

type ExtendedBase<K extends string> = [K] extends keyof [Base] ? never : {
    [key in K]: Base
} & Base

type Result = { displayName: string };

const f = <T extends string>(items: (T | 'self')[], obj: ExtendedBase<T>): Result[] => {
  return items
    .map(item => {
      if (item === 'self') {
        return { displayName: obj.displayName };
      }
      return {
        displayName: obj[item].displayName, //ok
      };
    });
};

const s = {
  id: '1234',
  displayName: 'Station 1',
  district: { displayName: 'Test', id: '1' },
  city: { displayName: 'Testcity', id: '1' },
};

const r = f(['city', 'self'], s); // Result[]

The Station type has been excluded as it serves no purpose in this context.

Answer №2

Check out this solution with detailed explanations in the comments

interface Base {
  id: string;
  displayName: string;
}

/**
 * This is a callback for Array.prototype.map
 */
type MapPredicate<
  Key,
  Obj extends Base,
  /**
   * Represents the first argument of applyBase function
   */
  Source extends Record<string, Base>
  > =
  /**
   * If Key is "self"
   */
  Key extends 'self'
  /**
   * Return obj.displayName
   */
  ? { displayName: Obj['displayName'] }
  /**
   * If Key extends keys of the first argument of applyBase
   */
  : Key extends keyof Source
  ? Source[Key] extends Base
  /**
   * return obj[item].displayName (see js implementation)
   */
  ? { displayName: Source[Key]['displayName'] }
  : never
  : never


/**
 * Map through a tuple and apply MapPredicate to each element,
 * just like it is done in runtime representation
 */
type Mapped<
  Arr extends Array<any>,
  Obj extends Base,
  Source extends Record<string, Base>
  > = {
    [Key in keyof Arr]: MapPredicate<Arr[Key], Obj, Source>
  }

const builder = (obj: { displayName: string }) =>
  ({ displayName: obj.displayName })

/**
 * Simple validation of the last argument (tuple of keys)
 * If elements extend either key of the first argument of applyBase function or "self"
 *  - they are considered as allowed keys, Otherwise - forbidden
 */
type Validation<Obj, Tuple extends unknown[]> = {
  [Key in keyof Tuple]: Tuple[Key] extends keyof Obj
  ? Tuple[Key]
  : Tuple[Key] extends 'self'
  ? Tuple[Key]
  : never
}

/**
 * The logic is straightforward, we need to infer each provided argument.
 */
function convert<
  BaseId extends string,
  BaseName extends string,
  BaseObj extends { id: BaseId, displayName: BaseName }
>(base: BaseObj): <
    NestedId extends string,
    NestedName extends string,
    Keys extends PropertyKey,
    Extension extends Record<Keys, { id: NestedId, displayName: NestedName }>,
    Items extends Array<Keys>
    >(obj: Extension, items: Validation<Extension, [...Items]>) => Mapped<[...Items], BaseObj, Extension>
function convert<
  BaseObj extends { id: string, displayName: string }
>(base: BaseObj) {
  return <
    Extension extends Record<PropertyKey, Base>,
    Items extends Array<PropertyKey>
  >(obj: Extension, items: Validation<Extension, [...Items]>) =>
    items
      .map(item =>
        item === 'self'
          ? builder(base)
          : builder(obj[item])
      )
}

const applyBase = convert(
  {
    id: '1234',
    displayName: 'Station 1',
  })

// const result: [{
//     displayName: "Testcity";
// }, {
//     displayName: "Station 1";
// }]
const result = applyBase(
  {
    district: { displayName: 'Test', id: '1' },
    city: { displayName: 'Testcity', id: '1' },
  }, ['city', 'self']);


Interactive Playground Link

If you're interested in argument inference, feel free to visit my article 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

Passing the product ID from Angular to another function in order to retrieve the corresponding product image ID

I'm trying to figure out how to send the ID of a product from a database to the getImage function. I want the function to use this ID to find and display the image associated with that product. HTML <div class="container-fluid first" style="cur ...

Determine the output of a function based on specific parameters, which will be included as a component of the returned object

I am currently encountering an issue that I am unsure if it can be resolved. function getOptions( period: { first: string; last: string }, prefix?: string ){ if (prefix) { return { [`${prefix}_first`]: formatDay(period.first), [`${pre ...

Angular 2 rc1 does not support ComponentInstruction and CanActivate

In the process of developing my Angular 2 application with Typescript using angular 2 rc.1, I've noticed that the official Angular 2 documentation has not been updated yet. I had references to ComponentInstruction Interface and CanActivate decorator ...

Depicting a potential value within Typescript

Coming from a background of working with functional languages that utilize monadic constructs like Option or Optional, I have noticed libraries such as fp-ts that provide these features in TypeScript/JavaScript. However, I am curious to understand how deve ...

The express-validator library raises errors for fields that contain valid data

I've implemented the express-validator library for validating user input in a TypeScript API. Here's my validation chain: export const userValidator = [ body("email").isEmpty().withMessage("email is required"), body(&quo ...

TypeScript is encountering difficulty locating a node module containing the index.d.ts file

When attempting to utilize EventEmitter3, I am using the following syntax: import EventEmitter from 'eventemitter3' The module is installed in the ./node_modules directory. It contains an index.d.ts file, so it should be recognized by Typescrip ...

Utilize the Lifecycle Interface within Angular 2 framework for enhanced application development

Can you explain the impact of this rule? "use-lifecycle-interface": true, ...

Angular: Issue with object instantiation - Unable to assign property

Having trouble populating my array due to instantiation issues. Defined Models: user: User = { firstName: "", lastName: "", address: "" } order: Order = { OrderId: "", User: this.user, TotalPrice: 0, OrderItems: [] } Attempting to populat ...

Utilizing props in styled components with Emotion-js and Typescript is not feasible

Check out this simple React component I created: import React, { ReactChild, ElementType } from 'react' import styled from '@emotion/styled' type WrapperPropsType = { size?: SizeType } type ButtonPropsType = { as?: ElementType< ...

Mastering the Art of Promises in RXJS Observables

After thoroughly researching SO, I stumbled upon numerous questions and answers similar to mine. However, I suspect that there might be gaps in my fundamental understanding of how to effectively work with this technology stack. Currently, I am deeply enga ...

What is preventing Typescript from inferring the type when assigning the output of a method with a return type to a variable?

My reusable service has a public API with documentation and types to make client usage easier. interface Storable { setItem(key: string, value: string): any; getItem(key: string): string; removeItem(key: string): any; } @Injectable({ providedIn: & ...

Is there a way to eliminate the right margin in React?

I am currently working with React to layout three elements below the topElement. My goal is to have these 3 elements fill up the space equally beneath topElement, removing the right-hand gap highlighted in red in the provided image (while keeping the gap a ...

After compiling typescript, ES6 Map.forEach is unexpectedly not a function

Exploring the new collection types introduced in ES6 for my TypeScript/React project. interface MyComponentProps{ myMap: Map<String, {isAvailable?: boolean}>, } ... this.props.myMap.keys(); Even though IntelliJ and Webpack compile the code withou ...

An unexpected error causes the entire application to come to a halt, specifically due to a property being undefined. Assistance is

Issue Reproduction: 1. We have a list of advertisers (our clients) for whom we run various marketing campaigns. 2. Upon clicking the "Campaign" button for a specific advertiser. Result: You are taken to the "campaigns" page displaying all campaigns for ...

Enhancing Password Strength Validation with Formik and Yup in a React Application

I am new to React and currently working on a signup page where I need to validate the password field using Regex. While utilizing Formik and Yup for validations, I encountered an issue where it shows the error that the property being called by the length ...

What is the best way to invoke a function in a class from a different class in Angular 6?

Below is the code snippet: import { Component, OnInit, ViewChild } from '@angular/core'; import { AuthService } from '../core/auth.service'; import { MatRadioButton, MatPaginator, MatSort, MatTableDataSource } from '@angular/mater ...

The expected React component's generic type was 0 arguments, however, it received 1 argument

type TCommonField = { label?: string, dataKey?: string, required?: boolean, loading?: boolean, placeholder?: string, getListOptionsPromissoryCallback?: unknown, listingPromissoryOptions?: unknown, renderOption?: unknown, getOptionLabelFor ...

Expanding one type by utilizing it as an extension of another type

I am looking to create a custom object with predefined "base" properties, as well as additional properties. It is important for me to be able to export the type of this new object using the typeof keyword. I want to avoid having to define an interface for ...

The TypeScript error code TS2339 is indicating that the 'modal' property is not recognized on the type 'JQuery'

I'm currently utilizing Typescript with AngularJS and have encountered an issue with modals when using the typed definition of jQuery library. The specific error message I am receiving is: 'error TS2339: Property 'modal' does not exist ...

In Typescript, how can we reverse the order of an enum

I am encountering a particular challenge with the following code snippet: enum MyEnum { Colors = 'AreColors', Cars = 'AreCars', } const menuTitle = ((obj: MyEnum) => { const newObj = {}; Object.keys(obj). ...