Navigate objects by typing

I am in search of a type that can describe any path through an object starting from the top (root) properties all the way down to every leaf property in the form of a string array. The following are the key characteristics required:

  • The object's structure is defined by an interface / type
  • There should be only a single path at a time, with no forks
  • The ability to stop at any level along the path
  • Selection limited to direct children, not allowing selection of sibling or ancestor nodes in the next step
  • If a property holds an array within the object, there should be an option to choose a specific array entry either using a predicate function or a numeric index, followed by continuing based on the type of array entries (assuming homogeneous arrays)
  • Optional properties should be treated as required for type checking purposes
  • Intellisense support providing correct suggestions for auto completion
  • The type should perform efficiently even with large objects / types

The type does not necessarily have to be recursive; it could have a predetermined number of nested levels where typing works before defaulting to "any" for deeper levels.

Complex coding tricks are acceptable as long as they deliver the desired functionality.

An example for clarity:

// Interface for the predicate function used to pick an array element:
export type PredicateFunction<ArrayType> = (array: ArrayType, index?: number) => boolean;

interface State  {
    city: {
        name: string;
        inhabitants: {
            firstname: string;
            lastname: string;
        }[],
        districts: string[]
    }
}

// Valid "paths"
const path1 = ['city'];
const path4 = ['city', 'name'];
const path2 = ['city', 'inhabitants'];
const path3 = ['city', 'inhabitants', 1];
const path3 = ['city', 'inhabitants', inhabitant => inhabitant.firstname === 'Max', 'lastname'];

// Invalid "paths"
const invalidPath1 = ['name'];
const invalidPath2 = ['city', 'inhabitants', 'firstname'];
const invalidPath3 = ['inhabitants', 1, 'firstname'];

Answer №1

Recently, I came across a helpful blogpost that may assist you in achieving something similar to what you are trying to do. The blogpost uses a period (.) to separate keys in a string, rather than using an array.

You can utilize a method for some type inference without having to create interfaces for all your data structures based on your specific use case.

I have implemented the type from the blogpost as follows:

type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
  ? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
  : never;

const deeply = {
  nested: {
    state: {
      isAvailable: true
    }
  }
}

function returnNestedValueOf<T extends {[key: string]: any}>(data: T, keys: NestedKeyOf<T>) {
    const separatedKeys = keys.split('.')
    let nested = data;

    separatedKeys.forEach((key: string) => {
        if (!!nested[key]) {
            nested = nested[key]
        }
    })

    return nested
}
// This will return { isAvailable: true }
const value = returnNestedValueOf(deeply, 'nested.state')

You can test it out in this playground link.

EDIT: A limitation of the above helper function is that TypeScript assumes the entire original object is being returned. Unfortunately, inferring the nested value remains a challenge.

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 initialize a value asynchronously for React context API in the latest version of NextJS, version

Currently, I'm working on implementing the React context API in my NextJS e-commerce application to manage a user's shopping cart. The challenge I'm facing is how to retrieve the cart contents from MongoDB to initiate the cart context. This ...

I encountered an issue when trying to call a class in angular 2, receiving the error message "Supplied parameters do not match any

A new class named items-class.ts was successfully created: export class ItemsClass { constructor(public name:string, public desc:string, public stat:string){} } To implement this class in a component called app.component.ts: import { Component } fro ...

Invoke the subscribe function within the encompassing parent function

In crafting a versatile method, I have devised the following code snippet: fetchArticle(loading: Loading): void { this.articleService.getArticleById(this.data.definition.id) .map((response: any) => response.json()) .subscribe((response: ...

Turn off the `noImplicitAny` TypeScript rule for a specific line of code

With "noImplicitAny": true enabled in my tsconfig.json, I encountered the TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}' error within my code space ...

The continuity of service value across parent and child components is not guaranteed

My goal is to update a value in a service from one component and retrieve it in another. The structure of my components is as follows: parent => child => grandchild When I modify the service value in the first child component, the parent receives t ...

What is the process for utilizing ts-node ESM in conjunction with node modules?

Disclaimer: I understand that the question below pertains to an experimental feature. I have initiated a thread on the ts-node discussion forum. Nonetheless, I believe that posting on StackOverflow will garner more visibility and potentially result in a qu ...

The term 'Employee' is typically used as a classification, but in this context, it is being treated as a specific value

I am encountering an issue while trying to define a variable as type Employee. The error message 'Employee' only refers to a type but is being used as a value here. ts(2693) keeps appearing. Here is my show-api.ts code: import { Component, OnIni ...

Guide to resolving a Promise that returns an array in TypeScript

My goal is to retrieve an array in the form of a promise. Initially, I unzipped a file and then parsed it using csv-parse. All the resulting objects are stored in an array which is later returned. Initially, when I tried returning without using a promise, ...

Cross-component communication in Angular

I'm currently developing a web-based application using angular version 6. Within my application, there is a component that contains another component as its child. In the parent component, there is a specific function that I would like to invoke when ...

Numerous toggle classes available

Having the following HTML inside a <span> element: <span (click)="openLeft()"></span> A method in a @Component sets a boolean variable like so: private isOpen: boolean; openLeft() { this.isOpen = !this.isOpen; } To toggle classes ...

Exploring alternative applications of defineModel in Vue 3.4 beyond just handling inputs

The examples provided for defineModel in the Vue documentation primarily focus on data inputs. I was curious if this functionality could be utilized in different contexts, potentially eliminating the need for the somewhat cumbersome props/emit approach to ...

Choosing everything with ngrx/entity results in not selecting anything

Issue with Selector I am facing an issue with my selector in the component. Despite making the call, the component does not update with books from the FakeApiService. The actions and effects seem to be functioning correctly. The problem seems to be relat ...

``Are you experiencing trouble with form fields not being marked as dirty when submitting? This issue can be solved with React-H

Hey there, team! Our usual practice is to validate the input when a user touches it and display an error message. However, when the user clicks submit, all fields should be marked as dirty and any error messages should be visible. Unfortunately, this isn&a ...

Issue in Angular: Attempting to access properties of undefined (specifically 'CustomHeaderComponent')

I have encountered a persistent error message while working on a new component for my project. Despite double-checking the injection code and ensuring that the module and component export logic are correct, I am unable to pinpoint the issue. custom-header ...

Guide on how to import or merge JavaScript files depending on their references

As I work on my MVC 6 app, I am exploring a new approach to replacing the older js/css bundling & minification system. My goal is to generate a single javascript file that can be easily referenced in my HTML. However, this javascript file needs to be speci ...

Typescript on the client-side: what is the best way to eliminate circular dependencies when using the factory method design pattern?

In my code, I have implemented the factory method pattern. However, some instances using this pattern end up with circular dependencies. Removing these dependencies has proven to be a challenge for me. To illustrate, consider the following example: // fact ...

Querying Firebase to find documents that do not have a specific requested field present in all

My current project is using firebase. I am currently working on retrieving documents from firebase, but I have encountered a specific issue. The problem lies in the fact that I have older documents without a "hidden" field and newer documents with this fie ...

What is the reason that TypeScript does not automatically infer void & {} as the never type?

When TypeScript's void type is used in combination with other types through intersection, the outcomes vary. type A = void & {} // A becomes void & {} type B = void & '1' // B becomes never type C = void & 1 // C becomes never type D = void ...

Utilizing the JavaScript Array.find() method to ensure accurate arithmetic calculations within an array of objects

I have a simple commission calculation method that I need help with. I am trying to use the Array.find method to return the calculated result from the percents array. The issue arises when I input a price of 30, as it calculates based on the previous objec ...

Using the ngClass directive with a conditional statement: Select 'class1' if true, otherwise select 'class2'

i am currently experimenting with this piece of code <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script> <button [ngClass]="{'btn': showDirectiveContent === false ? 'btn btn-danger ...