Utilizing a tuple for indexing in Typescript

Imagine you have a tuple containing keys like ["a", "b", "c"] and a nested object with these keys as properties {a: {b: {c: number}}}. How can you recursively access the members of the tuple as indices in typescript?

An example implementation without proper typing:

function recursivePluck(ob: any, tuple: any[]): any {
  for (let index of tuple) {
    ob = ob[index]
  }
  return ob
}

What would be the typed version of the code above?

I've attempted the following:

type RecursivePluck<
  Tuple extends string[], 
  Ob extends {[key in string]: any}, 
  TupleWithoutFirst extends SliceStartQuantity<Tuple, 1> = SliceStartQuantity<Tuple, 1>
>

= TupleWithoutFirst extends [] ? Ob[Tuple[0]] : RecursivePluck<TupleWithoutFirst, Ob[Tuple[0]]>

Unfortunately, this results in an error saying

Type alias 'RecursivePluck' circularly references itself.

Note that SliceStartQuantity is from typescript-tuple (npm)

Answer №1

Provided is a comprehensive solution that addresses the type safety of both argument and return types:

type Unshift<A, T extends Array<any>> 
= ((a: A, ...b: T) => any) extends ((...result: infer Result) => any) ? Result : never;
type Shift<T extends Array<any>> 
= ((...a: T) => any) extends ((a: any, ...result: infer Result) => any) ? Result : never;

type Revert
  <T extends Array<any>
  , Result extends Array<any> = []
  , First extends T[keyof T] = T[0]
  , Rest extends Array<any> = Shift<T>> = {
  [K in keyof T]: Rest['length'] extends 0 ? Unshift<First, Result> : Revert<Rest, Unshift<First, Result>> 
}[0]

// To prevent infinite processing by TypeScript
type Level = 0 | 1 | 2 | 3 | 4 | 5
type NextLevel<X extends Level> = 
X extends 0 ? 1 :
X extends 1 ? 2 :
X extends 2 ? 3 :
X extends 3 ? 4 :
X extends 4 ? 5 :
never

// This type gives us the possible path type for the object
type RecursivePath<Obj extends object, Result extends any[] = [], Lv extends Level = 0> = {
[K in keyof Obj]: 
Lv extends never ? Result :
Obj[K] extends object ? (Result['length'] extends 0 ? never : Revert<Result>) | RecursivePath<Obj[K], Unshift<K, Result>, NextLevel<Lv>> :
Revert<Result> | Revert<Unshift<K,Result>>
}[keyof Obj]

// Validate if the type is functioning correctly
type Test = RecursivePath<{a: {b: {c: string}, d: string}}>
type Test2 = RecursivePath<{a: {b: {c: {e: string}}, d: string}}>

// Retrieve the value type at a given path
type RecursivePathValue<Obj, Path extends any> = 
{
[K in keyof Path]: 
Path extends any[] ?
Path[K] extends keyof Obj ?
Path['length'] extends 1 ? Obj[Path[K]] : RecursivePathValue<Obj[Path[K]], Shift<Path>>
: never
: never
}[number]

// Validate if the type is functioning correctly
type Test3 = RecursivePathValue<{a: {b: {c: string}, d: string}},['a', 'b']>
type Test4 = RecursivePathValue<{a: {b: {c: {e: string}}, d: string}}, ['a','d']>

// The main function definition
function recursivePluck<Obj extends object, Path extends RecursivePath<Obj>>(ob: Obj, tuple: Path): RecursivePathValue<Obj, Path> {
// Use 'any' as a fallback inside
let result: any = ob;
for (let index of tuple as any[]) {
result = result[index]
}
return result;
}
const a = recursivePluck({a: {b: {c: {d: 'value'}}}}, ['a','b']) // OK
const b = recursivePluck({a: {b: {c: {d: 'value'}}}}, ['a','e']) // Error
const c = recursivePluck({a: {b: {c: {d: 'value'}}}}, ['a','b','e']) // Error
const d = recursivePluck({a: {b: {c: {d: 'value'}}}}, ['a','b','c']) // OK
const e = recursivePluck({a: {b: {c: {d: 'value'}}}}, ['a','b','c', 'd']) // OK

View Playground

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

Asynchronous NestJs HTTP service request

Is there a way to implement Async/Await on the HttpService in NestJs? The code snippet below does not seem to be functioning as expected: async create(data) { return await this.httpService.post(url, data); } ...

Issue with Angular 7: In a ReactiveForm, mat-select does not allow setting a default option without using ngModel

I have a Angular 7 app where I am implementing some reactive forms. The initialization of my reactive form looks like this: private initFormConfig() { return this.formBuilder.group({ modeTransfert: [''], modeChiffrement: [' ...

Having trouble with errors when adding onClick prop conditionally in React and TypeScript

I need to dynamically add an onClick function to my TypeScript React component conditionally: <div onClick={(!disabled && onClick) ?? undefined}>{children}</div> However, I encounter the following error message: Type 'false | (() ...

The functionality of d3 transition is currently non-existent

I've encountered an issue with my D3 code. const hexagon = this.hexagonSVG.append('path') .attr('id', 'active') .attr('d', lineGenerator(<any>hexagonData)) .attr('stroke', 'url(#gradi ...

Encountering unexpected errors with Typescript while trying to implement a simple @click event in Nuxt 3 projects

Encountering an error when utilizing @click in Nuxt3 with Typescript Issue: Type '($event: any) => void' is not compatible with type 'MouseEvent'.ts(2322) __VLS_types.ts(107, 56): The expected type is specified in the property ' ...

Issues with naming in Gulp, Angular2 Final, and Visual Studio: "Cannot find name" and "Duplicate identifier" errors

Recently, I updated my project to utilize the final release of Angular 2 and also upgraded Visual Studio to use TypeScript 2.0.3 from the Tool -> Extensions and Updates manager. I compile my TypeScript using Gulp, and it compiles without any errors. Howev ...

Understanding Typescript in Next.js components: Deciphering the purpose behind each segment

Consider the following function: type User = { id: string, name: string } interface Props { user: User; } export const getUserInfo: GetUserInfo<User> = async ({ user }: Props) => { const userData = await fetchUser(user.id); return ...

Best practices for managing backend errors with Next.js 14

Currently, I am developing a project in Next.js 14 and I have set up my API requests using fetch within a handler.tsx file as shown below: async function getPositions() { const response = await fetch( process.env.BASE_API_URL + "/positions?enabl ...

When trying to integrate Angular.ts with Electron, an error message occurs: "SyntaxError: Cannot use import statement

Upon installing Electron on a new Angular app, I encountered an error when running electron. The app is written in TypeScript. The error message displayed was: import { enableProdMode } from '@angular/core'; ^^^^^^ SyntaxError: Cannot use impor ...

Google Cloud PubSub does not automatically resend unacknowledged messages

The answer chosen for this particular question contains some pertinent details I currently have a subscription set up with the following parameters: https://i.stack.imgur.com/Bn0d4.png along with the following code snippet: const subscription = this.pub ...

The module './installers/setupEvents' could not be located within Electron-Winstaller

After encountering an error while attempting to package my Angular app on Windows 10, I'm looking for help in resolving the issue: https://i.stack.imgur.com/yByZf.jpg The command I am using is: "package-win": "electron-packager . qlocktwo-app --ove ...

Vue composable yields a string value

I am currently using a Vue composable method that looks like this: import { ref } from 'vue'; const useCalculator = (num1: number, num2: number, operation: string) => { const result = ref(0); switch (operation) { case 'add& ...

Substitute all attributes of objects with a different designation

I need to update all object properties from label to text. Given: [ { "value": "45a8", "label": "45A8", "children": [ { "value": "45a8.ba08", "label": "BA08", &q ...

Angular Reactive Forms - Adding Values Dynamically

I have encountered an issue while working with a reactive form. I am able to append text or files from the form in order to make an http post request successfully. However, I am unsure about how to properly append values like dates, booleans, or arrays. a ...

What is the best way to align a title above menu items within a Material UI app bar when using TypeScript and React?

Check out my current app bar design: app bar image Here is the inspiration for the app bar layout I'm aiming for (title above menu items): inspiration app bar goal This snippet showcases my code: import * as React from 'react'; // More cod ...

React with TypeScript presents an unusual "Unexpected token parsing error"

Recently, I encountered an issue with my helper function that was originally written in JavaScript and was functioning perfectly. However, as soon as I introduced TypeScript into the mix, strange behaviors started to surface. Here is the snippet of code fr ...

What is the best way to establish the primary color for the entire app?

Is there a way to easily set the color for @react-native-material/core's theme? I managed to change the color but I don't want to have to do it individually for each component. ...

The state of dynamically created Angular components is not being preserved

My current task involves dynamically creating multiple components to be placed in a table. The code successfully achieves this objective, but the state seems to be getting jumbled up at the level of the dynamically generated components. When a component is ...

One issue that may arise is when attempting to use ngOnDestroy in Angular components while rearranging user transitions

Encountered an issue recently with Angular - when the user navigates from component A to component B, component A remains active unless ngOnDestroy is triggered. However, if the user visits component B before going to component A and then leaves, ngOnDes ...

How can I conceal an element upon page load using Ionic framework?

index.component.ts initialize() { // Execute necessary functions after loading this.index.ready().then(() => { if(this.index.is('core')){ // this.menu.enable(false, 'mobileOnlyPages'); }else{ ...