String nested path TypeScript object type

Check out this get function I've written:

function get<T>(obj: T, props: (keyof T)[] | keyof T): any {
  const toArray = coereceArray(props);
  return obj && toArray.reduce(
    (result, prop) => result == null ? undefined : result[prop] as any,
    obj
  );
}

const result = get({ a: { b: 'hey' } }, ['a', 'b']);
const result2 = get({ a: { b: 'hey' } }, 'a'); 

I'm curious about dynamically typing the result based on the passed parameters. Any ideas?

Answer №1

This particular case presents an intriguing scenario. To gather all keys from both the main and nested objects, a recursive approach is necessary, leveraging the availability of recursive types in TypeScript. However, as we delve deeper into recursion, a potential challenge arises - specifically, the issue of "Type instantiation is excessively deep and possibly infinite." This can be problematic since the depth of the object remains unknown, prompting further exploration of this concern through the provided link to gain a better understanding.

To facilitate the gathering process effectively, imposing a limit on recursion becomes imperative. In this instance, a predefined limit of 5 has been set, offering room for expansion by replicating the established pattern.

Initially, defining the type:

type Level = 0 | 1 | 2 | 3 | 4 | 5 | 'max'; // addresses the infinite loop error
type NextLevel<Level> =
    Level extends 0 ? 1
    : Level extends 1 ? 2
    : Level extends 2 ? 3
    : Level extends 3 ? 4
    : Level extends 4 ? 5
    : 'max'; // enables iteration from 0 to 5, concluding with 'max'

type NestedKeyof<T, L extends Level = 0> = L extends 'max' ? never : {
    [K in keyof T]: T[K] extends object ? K | NestedKeyof<T[K], NextLevel<L>> : K
}[keyof T]

The NestedKeyof type serves as a mapped type, consolidating all keys within the map while extending the gathering process to nested objects when encountered. Notably, the recursive passage of the object T[K] alongside the incremental level adjustment provided by NextLevel<L> ensures holistic key extraction. The iterative journey concludes with the utilization of the 'max' type heralded by the initial declaration: L extends 'max' ? never.

A simple evaluation test showcases the accuracy of the type:

type NestedType = NestedKeyof<{ a: { b: 'hey', c: { d: 'elo' } } }>; // a | b | c | d

Subsequently, integrating it within your function follows suit:


function get<T, P extends NestedKeyof<T>>(obj: T, props: P[] | keyof T): any {
  const toArray = coereceArray(props);
  return obj && toArray.reduce(
    (result, prop) => result == null ? undefined : result[prop] as any,
    obj
  );
}

const result = get({ a: { b: 'hey' } }, ['a', 'b']); // correct
const result2 = get({ a: { b: 'hey' } }, 'a'); // correct
const result3 = get({ a: { b: 'hey' } }, ['a', 'b', 'c']); // expected error

While the NestedKeyof type operates efficiently within its designated constraints, there exist limitations due to the self-imposed cap delineated by Level and

NextLevel</code, catering up to 5 levels of nested objects. For intricate structures surpassing this threshold such as:</p>

<pre><code>type NestedTypeAboveMax = NestedKeyof
<{ a: { b: 'hey', c: { d: 'elo', e: { f: { g: { h: {i: 'test'}} } } } } }>;

Notably, the key i falls outside the specified limit and thus isn't considered. Should more extensive nested hierarchies be explored, contemplating extensions to Level and NextLevel becomes requisite to accommodate additional levels seamlessly.

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

Attempting to implement a typeguard in Typescript that relies on the presence of specific content within an element

Currently, I am attempting to develop a Typescript conditional that verifies if a particular word is already present in the name. The function in question is as follows: isOrganic() { for (let i = 0; i < this.items.length; i++) { if(this.ite ...

When using the `.push` method, the array becomes null

Currently, I am in the process of developing an angular application. Within my component's .ts file, there exists an array structured as follows: public myArray = []; public dataFromAPI = []; In a particular method within this component, whenever I ...

Angular - Conceal Protected Links on the Template

In my AuthGuard, I have implemented CanActivate which returns either true or false based on custom logic: import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular ...

How to Validate Ionic 2 Radio Button Selections with TypeScript

Imagine having a list like the one shown below: <ion-list radio-group [(ngModel)]="autoManufacturers"> <ion-list-header> Auto Manufacturers </ion-list-header> <ion-item> <ion-label>Cord</ion-label> &l ...

The 'cookies' property is not found on the 'Request' type

Currently, I am attempting to access a cookie within a NestJS controller. I have been referencing the documentation found at https://docs.nestjs.com/techniques/cookies#use-with-express-default Below is my implementation: import { Controller, Get, Render, ...

Pagination problem arises when sorting through items

I am facing an issue with filtering items on different pages of my React website. The problem I encounter is that the filtering functionality only works on the initial page where the data is loaded. Code: CustomersEpolicyPage.tsx import React, {useEffect ...

Having trouble establishing a connection with mongoose and typescript

When attempting to establish a connection using mongoose, I consistently encounter the errors outlined below. However, if I use MongoClient instead, everything functions as expected. import connectMongo from '../../lib/connectMongo' console.log( ...

Converting an anonymous function to a named function in JavaScript

In my Angular application, I have the following template: <dxi-column dataField="ordre" caption="Ordre" [width]="70" dataType="number" [allowEditing]="true"> <dxi-validation-rule type=" ...

What are the real-world applications of "Type-only Field Declarations"?

Source: Type-only Field Declarations. interface Animal { dateOfBirth: any; } interface Dog extends Animal { breed: any; } class AnimalHouse { resident: Animal; constructor(animal: Animal) { this.resident = animal; } } class DogHouse ext ...

The array is not being spliced in the DOM, however, it is being spliced in the console - Ionic 2+/Angular

My scenario involves a dynamic array filled with items and values. The goal is to remove an item from the view list when a user clicks a button on that particular item. I'm struggling to identify why this functionality isn't working as expected. ...

How to Delete an Item from an Array in BehaviorSubject Using Angular/Typescript

I have encountered an issue while trying to delete a specific element from my array upon user click. Instead of removing the intended item only, it deletes all elements in the array. I attempted to use splice method on the dataService object, but I'm ...

Is it possible to use @ViewChild to target an element based on its class name?

The author of this article on Creating Advanced Components demonstrates selecting an element by creating a directive first: @Directive({ selector: '.tooltip-container' }) export class TooltipContainerDirective {} Then, the author uses this d ...

How can I effectively test static navigationOptions using Jest and Enzyme in a React Navigation and TypeScript environment?

Currently, I am developing a React Native app using TypeScript. For component testing, I rely on Jest and Enzyme. Additionally, I have integrated React Navigation into my project. On one of the screens, the navigationOptions are as follows: static naviga ...

What is the most secure method to define options and retrieve their values in a type-safe manner?

I am currently utilizing a library that offers an interface with a great deal of flexibility. type Option = number | { x?: number; y?: number; z?: number; } interface Options { a?: Option; b?: Option; c?: Option; d?: Option; } function init ...

AngularFire UPDATE -> APPLY CHANGES

I can't seem to figure this out. I'm wondering how to UPDATE a document that is returned in the WHERE clause using AngularFire: constructor(private db: AngularFirestore) { } var path = this.db.collection('users').doc('type') ...

Issue encountered in Angular app-routing module.ts: Type error TS2322: The type '"enabled"' cannot be assigned to type 'InitialNavigation | undefined'

When I recently updated my project from Angular 11 to 14, I encountered the following error when running "ng serve". Error: src/app/app-routing.module.ts:107:7 - error TS2322: Type '"enabled"' is not assignable to type 'InitialNavigation | u ...

Can anyone provide guidance on creating a new type in Ionic Vue?

How can I define the description as a type in my code? When I run it, I get an error that says "Property 'description' does not exist on type '{ takePhoto(): Promise; }'" <script lang="ts"> import { IonPage, ...

Exploring Methods to Define Class Types as Parameter Types in Typescript

Is there a way to pass type information for a class, indicating to the compiler that the provided parameter is not an instance of a class but rather the definition of the class itself? import * as Routes from '../routes'; import * as Entities fr ...

Is it possible to create a tuple with additional properties without needing to cast it to any type?

To accommodate both array and object destructuring, I have defined the following `Result` type: type Errors = Record<string, string | null>; type Result = [Errors, boolean] & { errors: Errors; success: boolean }; I attempted to create a result of t ...

Broadening Cypress.config by incorporating custom attributes using Typescript

I'm attempting to customize my Cypress configuration by including a new property using the following method: Cypress.Commands.overwrite('getUser', (originalFn: any) => { const overwriteOptions = { accountPath: `accounts/${opti ...