What is the process for obtaining the form of an item and then adjusting the characteristics of each individual leaf property?

Consider this scenario:

interface SomeObject {
  prop1: number;
  prop2: string;
  prop3: {
    innerProp1: number[];
    innerProp2: string[];
    innerProp3: {
      deeperProp1: string[];
      deeperprop2: boolean;
    },
    innerProp4: {
      [key: string]: any;
    },
    innerProp5: {
      [key: string]: any;
    }
  }
}

I aim to define a type that can take the shape of any object and return the same structure with specified types for the "leaf" properties. Additionally, each property should be optional. Here is an example of what I'm looking for:

type ModifyShapeType<Shape, NewType> = ???

When applied to the interface above, it should provide type safety for the identical shape but with the desired type:

const myObject: ModifyShapeType<SomeObject, boolean> = {
  prop1: true;
  prop2: true;
  prop3: {
    // innerProp1: true;
    // innerProp2: false;
    innerProp3: {
      deeperProp1: true;
      // deeperprop2: true;
    },
    innerProp4: true,
    // innerProp5: false
  }
};

I have formulated a solution, but I want to remove the original types from the structure and substitute them with the desired types while maintaining specificity on property access. This is what I have so far:

type ModifyShapeType<S, T> = Partial<Record<keyof S, Partial<S[keyof S] | T>>>;

For a live demonstration, check out the TypeScript Playground.

Current limitations include:

  1. The types are still inferred from the original object type, resulting in mixed types.
  2. All properties now share the same type (loss of specificity on read), leading to unsafe writes as well (loss of specificity on write).

Is achieving this goal feasible?

Answer №1

It sounds like you require a complex, recursive data structure.

To achieve this, the process involves iterating through each key and determining if it is an object (branch) or another type of value (leaf). If it's a branch, recursion is necessary. If it's a leaf, the desired value type should be output. The challenge lies in defining what constitutes a leaf, as every value in JavaScript has properties and can behave somewhat like an object.

In order to accomplish this task, you'll need a conditional branch detection type and a recursive mapped type.

// Determines if T is a branch or a leaf
type IsBranch<T> = 
  T extends { [k: string]: any } ? 
    T extends any[] ? 
      never :
      T :
    never

// Recursively processes each key
// For branches, process keys and return Partial of that branch
// For leaves, replace with the specified value type
type ModifyShapeType<S, T> = S extends IsBranch<S> ?
  Partial<{ [k in keyof S]: ModifyShapeType<S[k], T> }> :
  T

const a: ModifyShapeType<{ a: number }, boolean> = { a: true }
const b: ModifyShapeType<{ a: number[] }, boolean> = { a: true }
const c: ModifyShapeType<{ a: { b: number } }, boolean> = { a: { b: true } }

Try it out in the TypeScript Playground

An interesting aspect to consider is how an array type differs from other types, requiring unique handling.

Another challenge arises when discerning between { a: number } and { [k: string]: number }. Determining whether to treat one as a branch and the other as a leaf poses difficulties, especially when considering conditional tests on T[string] for indexable properties. This distinction may prove elusive, warranting further exploration.

Answer №2

This code snippet is quite reminiscent of RecursivePartial, but with a twist. Instead of using any, it takes into account array subtypes. You can find more information on this topic here: Recursive Partial<T> in TypeScript

What sets this apart is its ability to replace leaf nodes of T with V, making it a useful tool for certain types of data manipulation without resorting to the use of any:

/**
 * Replaces leaf nodes of T with V
 */
export type RecursiveReplace<T,V> = {
  [P in keyof T]: T[P] extends (infer U)[]
    ? RecursiveReplace<U,V>[]
      : T[P] extends number | string | symbol | undefined
      ? V
      : RecursiveReplace<T[P],V>;
};

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

Obtain precise measurements of a modified image using the Sharp library

My Cloud Function successfully resizes images uploaded to Cloud Storage using Sharp. However, I am facing an issue with extracting metadata such as the exact height and width of the new image. I am contemplating creating a new function that utilizes diff ...

Is it possible to eliminate a parameter when the generic type 'T' is equal to 'void'?

In the code snippet below, I am attempting to specify the type of the resolve callback. Initially: Generic Approach export interface PromiseHandler<T> { resolve: (result: T) => void // <----- My query is about this line reject: (error: a ...

Angular 2: A guide to setting up `--module system --experimentalDecorators` flags in your project

I am encountering an issue while compiling my TypeScript file: app/app.component.ts import {Component} from 'angular2/core'; @Component({ selector: 'my-app', template: '<h1>Messenger</h1>' }) export clas ...

What is the abbreviation for a 'nested' type within a class in TypeScript?

Consider the TypeScript module below: namespace AnotherVeryLongNamespace { export type SomeTypeUsedLater = (a: string, b: number) => Promise<Array<boolean>>; export type SomeOtherTypeUsedLater = { c: SomeTypeUsedLater, d: number }; } cl ...

Looking for a solution to the error message: "X is not able to be assigned to the type 'IntrinsicAttributes & Props'"

Greetings everyone! I need some assistance in setting up single sign-on authentication using React, Azure AD, and TypeScript. I'm encountering a type error in my render file and I'm unsure of how to resolve it. Below is the specific error message ...

Understanding Angular's Scoping Challenges

I have a function that retrieves an array and assigns it to this.usStates. main(){ this.addressService.getState().subscribe( (data:any)=>{ this.usStates = data; if(this.usStates.length===0) { this.notificationServic ...

Checking the interceptor response in NestJs testing

I created a basic interceptor that removes a specific field from a response: import { CallHandler, ExecutionContext, Injectable, NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } ...

Exploring the World of ESLint, Prettier, Typescript, and VScode Configuration

There is a common belief that Prettier is the preferred tool for formatting, while ESlint is recommended for highlighting linting errors, even though ESlint also has formatting capabilities. However, it should be noted that Prettier lacks certain advanced ...

Utilizing Angular to import an SVG file from a backend and incorporate its content as a template

I am looking for a solution to load an SVG date from my Spring Boot backend and utilize it as an Angular template. Currently, the request is structured like this: getSVG (): Observable <any> { return this.http.get(`${environment.apiUrl}/path ...

Check out the uploaded file preview on React Native Expo!

I'm attempting to display a preview of the file uploaded by the user, which could be in pdf, img, or doc format. I tried a method that previews the file using a specific URL, but what I really want is for it to only show the preview of the uploaded fi ...

react-vimeo not firing onPause and onPlay events

I am facing an issue with triggering props when playing a Vimeo video on my webpage. Here's a snippet of my code: import Vimeo from '@u-wave/react-vimeo'; const handleVimeoProgress = (data: any) => { console.log('Progress:' ...

Encountering a runtime issue with socket.io when using typescript that has been bundled by

Recently, I attempted to implement web sockets using socket.io in a Node server written in TypeScript with ExpressJS and bundled with Webpack. The server code is structured as follows: import * as Express from "express"; import * as SocketIO from "socket ...

Is it possible to access NgbdModalContent properties from a different component?

If I have a component with a template containing an Edit button. When the user clicks on it, I want to load another component as a dynamic modal template. The component is named ProfilePictureModalComponent and it includes the Edit button: (Angular code h ...

There was a Runtime Error that occurred, stating a TypeError: It is not possible to access properties of an undefined value (specifically

I've encountered an issue with a donut chart implemented from react-apex charts. Every time I try to render the page containing the chart, an error occurs. However, if I make changes to a property of the chart, it renders without any errors on the fro ...

Is there a way to substitute the HOC with a single call and solely modify the prop?

One issue I've encountered in my project is the repetitive use of a Higher Order Component (HOC) for the header. Each time it's used, the props are set to determine whether header links should be displayed or not. My objective is to streamline th ...

Issue encountered while attempting to package Azure project in Visual Studio 2015 Update1 due to difficulty copying Typescript files

Since upgrading to VS 2015 Update 1 (that includes Typescript 1.7) and Azure SDK 2.8, packaging my Azure application for deployment has become a challenge due to an error in the file path where the packager is attempting to copy the js output file: Erro ...

Combining the namespace and variable declarations into a single statement

Currently, I am facing an issue with creating a declaration file for the third-party library called node-tap. The main challenge lies in properly declaring types for the library. // node_modules/a/index.js function A() { /* ... */ } module.exports = new A ...

Is there a marble experiment that will alter its results when a function is executed?

Within Angular 8, there exists a service that contains a readonly Observable property. This property is created from a BehaviorSubject<string> which holds a string describing the current state of the service. Additionally, the service includes method ...

Error encountered when attempting to retrieve token from firebase for messaging

I am currently working on implementing web push notifications using Firebase. Unfortunately, when attempting to access messaging.getToken(), I encounter an error stating "messaging is undefined." Below is the code snippet I am utilizing: private messaging ...

Retrieving Data from Vuetify Component within vue 3

Currently, I am in the process of creating my own wrapper for Vuetify components to eliminate the need to repeatedly define the same props in each component. For example, I aim to develop a custom TextField with defaultProps while still being able to accep ...