Setting the data type of a value in a deeply nested object path using Typescript

I need to figure out how to determine the value types for all keys within nested object paths.

While I have been successful in most cases, I am struggling with setting the value type for a deep nested property inside an array object.

interface BoatDetails {
  boats: {
    boat1: string;
    boat2: number;
  };
  ships: Array<Ship>
}
interface Ship {
    shipName: string
}
const boatDetails: BoatDetails = {
  boats: {
    boat1: "lady blue",
    boat2: 1,
  },
  ships: [
      {shipName: "lacrose"}
  ]
};

In the provided code snippet, I have successfully assigned value types to nested object paths such as boats.boat1 (string), boats.boat2 (number), and ships (Array<Ship>).

However, I am facing difficulty in setting the value type for ships.0.shipName.

To learn more about assigning value types to deep nested object paths, I referred to this link: Typescript: deep keyof of a nested object

Here is my attempt at setting the value type for deep nested object paths using TypeScript Playground:

Playground link for seeting value type for deep nested object paths

Answer №1

Caution: the usage of recursive conditional types combined with manipulation of template literal type within Paths<T> and PathValue<T, P> can strain the compiler (leading to potential recursion limit warnings or extensive compile times) and involves various edge cases.


One particular challenge arises when attempting to convert from number to string through literal types using template literals as there is no straightforward way to inversely transform string literals back into corresponding number literals (refer to this question and answer for more insight).

For instance, trying to use an index type like "0" as a key in an array type will result in an error unless that array type is a tuple:

type Oops = (string[])["0"] // error!
// ------------------> ~~~
// Property '0' does not exist on type 'string[]'

type Okay = (string[])[0] // okay
// type Okay = string

This limitation causes issues when using expressions like "ships.0.shipName" because expectedly, "0" fails to be recognized as a valid key in an array. This lack of support leads to frustration as there is no direct method to coerce "0" into 0 or have "0" interpreted as keyof Ship[].


To circumvent this obstacle, several workarounds exist. One approach is to overlook tuples (which usually have explicit numeric-string indices except for tuple types containing rest elements) and create a workaround utilizing T[K] checking for a number index signature where K is compatible with `${number}`, returning T[number] if so:

type Idx<T, K> = K extends keyof T ? T[K] :
    number extends keyof T ? K extends `${number}` ? T[number] : never : never;

This solution proves effective:

type TryThis = Idx<string[], "0">
// type TryThis = string

type StillWorks = Idx<string[], 0>
// type StillWorks = string

By incorporating this in your PathValue<T, P> type as shown below:

type PathValue<T, P extends Paths<T, 4>> = P extends `${infer Key}.${infer Rest}`
  ? Rest extends Paths<Idx<T, Key>, 4>
  ? PathValue<Idx<T, Key>, Rest>
  : never
  : Idx<T, P>

The functionality improves significantly:

setValue(
  boatDetails,
  `ships.0.shipName`,
  "titanic"
); // okay
/* function setValue<BoatDetails, "ships.0.shipName">(
     obj: BoatDetails, path: "ships.0.shipName", value: string
): BoatDetails */

While other potential workarounds may offer more precise outcomes for arbitrary pairs of T and K, the current implementation suffices for the time being.

Explore the code on the TypeScript 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

Is it possible to convert a type to a JSON file programmatically?

Recently, I have been tasked with implementing configuration files for my system, one for each environment. However, when it came time to use the config, I realized that it was not typed in an easy way. To solve this issue, I created an index file that imp ...

Stop useEffect from triggering during the first render

I'm working on implementing a debounce functionality for a custom input, but I'm facing an issue where the useEffect hook is triggered during the initial render. import { useDebouncedCallback } from "use-debounce"; interface myInputProps { ge ...

Tips for designing a custom TypeScript 5 property decorator

I have a decorator in TypeScript: const bindMethod = (method: any): PropertyDecorator => ((target: any, name?: PropertyKey): any => { if(name === undefined) { throw new Error('Bound decorator must be used with a property name.& ...

Encountering unexpected compilation errors in an Angular 9 project while utilizing safe null accessing and null coalescing features?

It's really strange what happened with this project. It was working perfectly fine yesterday, and I even left 'ng serve' running after finishing my work without any issues. However, today when I tried to compile the app, I ran into problems ...

data not corresponding to interface

I am encountering an issue that says Type '({ name: string; href: string; icon: IconDefinition; } | { name: string; href: string; icon: IconDefinition; childs: { name: string; href: string; icon: IconDefinition; }[]; })[]' is missing the followin ...

A step-by-step guide on reversing options in the Ant Design Cascader component

By default, the Cascader component's options are nested from left to right. I am looking to have them go from right to left instead. However, I could not find anything in the component's API that allows for this customization. Is it even possibl ...

The Next.js template generated using "npx create-react-app ..." is unable to start on Netlify

My project consists solely of the "npx create-react-app ..." output. To recreate it, simply run "npx create-react-app [project name]" in your terminal, replacing [project name] with your desired project name. Attempting to deploy it on Netlify Sites like ...

Is it recommended for TypeScript to automatically resolve the index.ts file as the default module file?

Struggling with getting the module resolution to work in TypeScript. Consider the following file structure: /modulename/index.ts Should it be resolved like this? import * as modulename from "modulename" I can't seem to make it work. However, imp ...

What is the syntax for implementing React.forwardRef in a dynamic Anchor or Button component?

I am working on a component that can act as either a button or an anchor tag. However, I am facing challenges in implementing conditional typing for the ref. How can I resolve this issue and make the ref acceptable? type ConditionalElements = | ({ ...

Reactivity in Angular Autocomplete with API Integration

I went through all the tutorials on Angular Autocomplete using API to follow the steps. I implemented valueChanges to monitor the form control, used switchMap to send a new request with each keyword change, and then displayed the data in the autocomplete d ...

javascript identify dissimilarities within arrays

Working on an Angular 2 application and attempting to identify the difference between two arrays (last seven days and missing dates within the last seven days). Everything works fine when initializing the array through a string, like in example code 1. How ...

Generate a pre-signed URL for an AWS S3 bucket object using Typescript in NextJS, allowing for easy file downloads on the client-side using @aws-sdk/S3Client

In the utilization of v3 of the S3Client, it appears that all existing examples are based on the old aws-sdk package. The goal is for the client (browser) to access a file from S3 without revealing the key from the backend. From my research, it seems tha ...

The correlation between the node.js version and the @types/node version

I recently started exploring node.js and decided to experiment with using TypeScript alongside it. After running npm install @types/node, I found that the latest version available was 7.0.4: $ npm install @types/node <a href="/cdn-cgi/l/email-protectio ...

Mysterious issue arises during deployment of Typescript-React application on Heroku

I am working on a TypeScript-React application generated using create-react-app. Deploying it to Heroku is proving to be a challenge as the process fails with an error message during installation and build: remote: Creating an optimized production b ...

Passing extra arguments to a callback function in Typescript

I'm trying to pass a parameter to a callback function. Below is the snippet of my function: let func = function(el, index){ if(el.id === myId) return index; } arr = [obj1, obj2, obj4, ...]; arr.filter(func); Is there a way to suc ...

It appears that Yarn is having trouble properly retrieving all the necessary files for a package

Recently, I encountered a strange issue in our project involving 3 microservices, all using the exceljs library. Two of the microservices successfully download all necessary files for this package through yarn. However, the third microservice is missing ...

Error: No injection provider found for function(){}!

After countless hours of setting up a custom AOT in Angular 7 project without CLI and debugging, I have encountered the following error: Uncaught Error: StaticInjectorError(Platform: core)[function(){}]: NullInjectorError: No provider for function(){}! ...

Is it necessary to include @types/ before each dependency in react native?

I am interested in converting my current react native application to use typescript. The instructions mention uninstalling existing dependencies and adding new ones, like so: yarn add --dev @types/jest @types/react @types/react-native @types/react-test- ...

Keep track of the input values by storing them in an array after they are

Creating an Angular application to read barcodes. barcode-scanner.component.html <form #f="ngForm" class="mt-3 text-center" id="myform" (ngSubmit)="onSubmit(f)"> <div class="text-center"> <input type="text" maxlength= ...

What is the procedure for utilizing custom .d.ts files in an Angular 15 project?

Currently, within my Angular 15 project, I am utilizing a package called bootstrap-italia. This particular package is dependent on the standard Bootstrap package and includes additional custom components and types. However, it should be noted that this pac ...