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

What is the best way to showcase a variable from a typescript file in an HTML file with Angular?

In my TypeScript file, I have the following function: ngOnInit() { if (sessionStorage['loyalPage']) { this.page = Number(sessionStorage['loyalPage']); } this.webService.getLoyalPlayers(this.pag ...

Combining platform-express and platform-fastify for optimal performance

I am currently working on a NestJS application and my goal is to upload files using the package @types/multer. However, I encountered an issue while following the guidelines from the official documentation: Upon starting my application development, I dec ...

Alert: Attempting to access an undefined value in an indexed type

I would like to find a way in Typescript to create a hashmap with indexable types that includes a warning when the value could potentially be undefined during a lookup. Is there a solution for this issue? interface HashMap { [index: number]: string; } ...

Issues with Imported Routes Not Functioning as Expected

I am currently working on implementing routing in my Angular 2 project. All the components are functioning properly, but I encounter an error when I include 'appRoutes' in the imports section of app.module.ts. An unexpected TypeError occurs: C ...

Rearrange an element in an array from last to first position using typescript

I am working with an array that looks like this var column = ["generic complaint", "epidemic complaint", "epidemic1 complaint", "epidemic2 complaint", "bal vivah", "name"] My goal is to move the last element of the array to the first position, resultin ...

Next.js is perplexing me by throwing an error about Event handlers not being able to be passed to Client Component props, even though the component clearly has "use client" at

My bundler generates a basic React component like this "use client"; "use strict";var a=Object.create;var r=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var i=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,s=Objec ...

It only takes half a minute for ts-node-dev to set up its watchers

Having tried both ts-node and ts-node-dev, I am frustrated by the fact that they take almost the same amount of time to start my app server. This issue has been a recurring problem for me, with no successful attempts at resolving it. Currently, I am opera ...

Managing multiple asynchronous requests through Observables in web development

I am working on an Angular2 website that sends multiple ajax requests using Json Web Tokens for authorization when it is initialized Here are two examples: public getUser(): Observable<User> { // Code block to get user data } public getFriends ...

What is the best approach for handling server-side validation errors in Angular when making an HTTP call?

After following different tutorials, I have created a service that can transmit login details to the backend for validation and processing. Although I am able to generate appropriate error codes based on user input, I find myself wondering what to do next. ...

Having trouble reloading a seekbar (input range) in Angular 7 with a function?

I am currently in the process of developing a music player using Angular 7, and below is the HTML code for my component: <div class="track-controller"> <small>{{musicPlayerService.getCurrentTime()}}</small> <div class="progress- ...

declare wrong TypeScript type in external library

I am currently using winston 3.0 in combination with the @types/winston types. Unfortunately, it seems that these types are not completely compatible, leading to an error in the types that I am unsure how to rectify. Below is the code snippet in question: ...

Input values that are true, or in other words, fulfill conditions for truthiness

Is there a specific data type in TypeScript to represent truthy values? Here's the method I'm working with: Object.keys(lck.lockholders).length; enqueue(k: any, obj?: any): void It seems like TypeScript allows checking for empty strings &ap ...

Unable to add chosen elements to array - Angular material mat select allowing multiple selections

Can anyone assist me in figuring out what I am doing wrong when attempting to push data to an empty array? I am trying to only add selected values (i.e. those with checked as true), but I can't seem to get inside the loop This is the current conditi ...

Discovering the minimum and maximum values defined by the user in a list of tuples

I am currently working with a collection of tuples, each containing 2 integers: data_list=[(20, 1), (16, 0), (21, 0), (20, 0), (24, 0), (25, 1)] My goal is to identify and retrieve the tuple that has the smallest second integer value but the largest firs ...

Incapable of acquiring the classification of the attribute belonging to the

Is it possible to retrieve the type of an object property if that object is stored in a table? const records = [{ prop1: 123, prop2: "fgdgfdg", }, { prop1: 6563, prop2: "dfhvcfgj", }] const getPropertyValues = <ROW extends o ...

The function for batch insertion only functions with Postgresql and SQL Server databases

I am a beginner in JavaScript and I am currently working on creating a new restaurant. I have come across a code snippet that inserts a relation into a join-table: await newRestaurant.$relatedQuery('tags', trx).relate(tagIds); Is it not possible ...

Tips for sending user IDs through an Angular 8 interceptor

Alright, In my Angular application that is using version 8, I have an HttpMaintenanceInterceptor configured without the use of cookies. Instead, I have a getAccessToken method within the authService as shown below: getAccessToken(): string { return ...

Incorporate JavaScript Library into StencilJs Using TypeScript

Recently, I decided to incorporate a JavaScript library called Particles.js into my project. The process involved importing it and initializing it within my component: import { Component, h } from '@stencil/core'; import * as particlesJS from &a ...

Is there a way to extract the HTMLElement[] type from the DOM?

In my TypeScript project, I am trying to gather all the top-level elements of a page using the code snippet below: const getHTMLElement() : HTMLElement[] { const doc = document.body.children; const list : HTMLElement[] = []; for (let c of Array.f ...

What is causing the geolocation heading to remain "null" on Android devices when using Chrome?

Recently, I developed a compact geolocation watch using the following code snippet: navigator.geolocation.watchPosition( this.updateLocation.bind(this), this.errorLocation.bind(this), {enableHighAccuracy: true} ); The function updateLocation ...