Exploring the depths of Typescript: Navigating through intricate mapping of keys and types in dynamic

Explaining this may be a bit tricky, but I'll start with stating my objective and then elaborate on the question as clearly as possible:

Note: The version of typescript doesn't matter to me, any solution would be appreciated.

interface Type {}
interface ExtendedType1 extends Type {}
interface ExtendedType2 extends Type {}
interface Type2 {}

type Lookup<T> = { [key: number]: T } | { [key: string]: T };

// The part causing me trouble
// and the type error that I'm struggling to resolve.
export type DeepKeyMap<TLookupValue, TLookup extends TLookupValue | Record<string, TLookupValue>, TMappedValue> =
  | TLookup extends TLookupValue ? TMappedValue : { [key in keyof TLookup]: DeepKeyMap<TLookupValue, TLookup[keyof TLookup], TMappedValue> };

interface ITest<TType extends Lookup<Type[]>> {
  data: TType;
}

class Test<TType extends Lookup<Type[]>, TOther extends DeepKeyMap<Type[], TType, Type2>> {
  source: ITest<TType>;
  mapped: TOther;

  constructor(options: ITest<TType>) {}
}

const t = new Test({
  data: {
    test: {
      check: [{} as ExtendedType1]
    },
    test2: {
      check2: [{} as ExtendedType2]
    }
  }
});

t.mapped.test.check; // <- The expected answer should be Type2

Basically, I am attempting to create a deep mapping of types where one object will determine the keys of another object using as much inference as possible. In the given example, the challenging part for me is the

DeepMap<Lookup<T>, MappedType>
type.

I have searched extensively online for similar examples, but none seem to address nested keys as deeply as I require.

So, I have reached the point of seeking help here. Thank you in advance for any assistance!

Edit: I have consolidated and fixed the example to reflect the current progress of my efforts.

Answer №1

To achieve the recursive traversal of properties in a type T and replace properties from source type S to destination type D in TypeScript, one can define a DeepMap like this:

type DeepMap<T, S, D> = { [K in keyof T]: T[K] extends S ? D : DeepMap<T[K], S, D> }

Testing this requires unique properties in the example types, as all empty types are considered equivalent. So, additional properties are added to the example types to ensure structural distinctiveness:

class Type { t = "T" }
class ExtendedType1 extends Type { et1 = "" }
class ExtendedType2 extends Type { et2 = "" }
interface Type2 { type2: number; }

In the MyClass constructor, the options parameter must be of type {data: T} for a generic T, and the mapped property should be of type DeepMap<T, Type[], Type2>:

class MyClass<T extends object> {
  mapped!: DeepMap<T, Type[], Type2>; // need to implement this
  constructor(options: { data: T }) { }
}

It's important to note that mapped is not initialized, so runtime behavior may not be as expected. Further testing can be done to verify if test's type is correct:

const test = new MyClass({
  data: {
    test: {
      check: [new ExtendedType1()]
    },
    test2: {
      check: [new ExtendedType2()]
    }
  }
});

test.mapped.test.check; //Type2

This implementation may not cover all edge cases where DeepMap behaves differently, such as with union-typed or function-valued properties in T assignable to S. Nonetheless, it provides a direction for implementation. Good luck!

Link to code

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 preventing Angular from requiring an additional tag for a child component

Consider a scenario where I have a parent and child component in Angular 12. Their templates are structured as follows: Parent: <h1>This is the parent component</h1> <div class="container"> <div class="row"> ...

Anticipating the outcome of various observables within a loop

I'm facing a problem that I can't seem to solve because my knowledge of RxJs is limited. I've set up a file input for users to select an XLSX file (a spreadsheet) in order to import data into the database. Once the user confirms the file, v ...

FabricJS Canvas with a React DropDown Feature

While I have successfully created a TextBox on FabricJS Canvas, creating a Dropdown component has proven to be a challenge. The fabric.Textbox method allows for easy creation of text boxes, but no such built-in method exists for dropdowns in FabricJS. If y ...

Utilize Typescript to aim for the most recent edition of EcmaScript

Are you looking to configure your typescript build to compile to the most recent version or the most current stable release of EcmaScript? For example, using this command: tsc --target <get latest version> Alternatively, in a tsconfig file: { & ...

It appears that the functions in linqts are not clearly defined

Currently, I am in the process of learning Angular4 and facing challenges with getting linqts to function properly. Within my room-list.component.ts file, I include it in this manner: import { List } from 'linqts'; A few lines below, I have my ...

Can you please provide instructions on how to obtain the TypeScript definitions file for a specific version of Jquery, such as 3.2.1

As I navigate my way through TypeScript, I find myself in need of accessing type definitions for a jQuery project in Visual Studio. The project currently utilizes jquery version 3.2.1, and I'm on the lookout for TypeScript type definitions for it. Af ...

Encountering an issue while developing a Discord bot using TypeScript

Hello, I'm currently working on creating a nick command for my discord bot in TypeScript, but I encountered an error. Here is the issue: Error: Expression expected.ts (1109) When I replace const mentionedMember = message? message.mentions.members? ...

Is it possible for TypeScript to automatically determine the specific type that was used in a union type parameter?

I need some help with a utility function I'm working on that can remove a specified number of elements from either a string or an array. My goal is to have the compiler determine whether the return value should be a string or an array based on what is ...

Tips for stopping </p> from breaking the line

<p>Available: </p><p style={{color:'green'}}>{props.active_count}</p><p>Unavailable: </p><p style={{color:'red'}}>{props.inactive_count}</p> https://i.sstatic.net/NQo5e.png I want the ou ...

Assigning a Boolean value of false in JavaScript will result in it being evaluated as true

I'm struggling to understand why my variable continues to evaluate as true and enters the IF block, even after I specifically assign it as false. let isSetToFalse = this.model.hasQ1; console.log('type ', typeof this.model.hasQ1) //I find it ...

Issue with Angular 7 cli failing to recognize a custom TypeScript file

While working on an Angular 7 component, I encountered an issue when trying to read a custom file. The problem arises when the server restarts, even though there are no errors in the component's TypeScript file. ERROR: app/zontify-components/zonti ...

Understanding File Reading in Angular/Typescript

I'm currently developing an app similar to Wordle and I'm facing an issue with reading words from a file. Here's what I tried: import * as fs from 'fs'; const words = fs.readFileSync('./words.txt', 'utf-8'); con ...

Validating a field conditionally upon submission

Adding a required validation conditionally to the "imageString" field upon submission, but the expected required validation is not being set. Initializing the form. constructor(){ this.poeForm = this.fb.group({ imageString: [""], imageFileNam ...

Can the return type of a function be determined dynamically at runtime by analyzing a function parameter value?

Let's delve into this concept with an illustrative example! interface Query { foo: any; } interface Mutation { bar: any; } type OperationName = keyof Query | keyof Mutation; const request = async <T>(args, operationName: OperationName): P ...

Having difficulty running lint on Vue 3 TypeScript application, but building process is successful

We are encountering an issue at the moment. We can successfully build our app, but we are facing challenges with linting using the vue tools (vue-cli-service ...). The hot-reloading feature works initially, but upon saving a file, we receive an error mess ...

Having trouble showing JSON data with Ionic 2 and Xcode?

Extracting JSON data from a JSON file in my project and viewing it using "ionic serve" on my Mac has been successful. However, I am facing an issue after building for IOS in XCode. I import the generated project into XCode as usual, but the JSON data is no ...

The property 'supabaseUrl' cannot be destructured from 'getConfig(...)' because it is not defined

I recently went through the tutorial provided by @supabase/auth-helpers-sveltekit on integrating supabase-auth helpers with sveltekit. However, upon running the development server, I encountered an internal error. Cannot destructure property 'supabas ...

Angular 2 Guide on macOS Sierra Shows "Page Not Found" Error in Web Browser

I've been delving into Angular 2 with TypeScript on my OS X machine. Following the tutorial to the T, I didn't encounter any errors during compilation. Upon executing the npm start command, everything seemed to be smooth sailing as a new tab open ...

What is the significance of `new?()` in TypeScript?

Here is a snippet of code I'm working with in the TypeScript playground: interface IFoo { new?(): string; } class Foo implements IFoo { new() { return 'sss'; } } I noticed that I have to include "?" in the interface met ...

Ionic - Deleting an item from local storage

Currently, I am implementing local storage for my Ionic application. While I can successfully add and retrieve data from local storage, I encounter an issue when trying to delete a specific object - it ends up removing everything in the database. Moreover, ...