Flatten a specific property of an object recursively

If I have a data structure containing nested objects, I need to create a type that removes specific keys and flattens certain fields recursively

Input:

{
    sys: {
        id: string;
    };
    metadata: {
        author: string;
    };
    fields: {
        name: string;
        age: number;
        father: {
            sys: {
                id: string;
            };
            metadata: {
                author: string;
            };
            name: string;
        },
        siblings: {
            sys: {
                id: string;
            };
            metadata: {
                author: string;
            };
            name: string;
        }[]
    };
}

Desired Output:

{
    name: string;
    age: number;
    father: {
        name: string;
    };
    siblings: {
        name: string;
    }[];
}

To achieve this, I attempted to create a type that filters out 'sys' and 'metadata' keys and recursively processes the 'fields' key:

type Helper<T extends object> = {
    [K in keyof T]: K extends "sys" | "metadata"
        ? never
        : K extends "fields"
        ? ...
        : T[K] extends Array<infer I>
        ? I extends object
            ? Helper<I>
            : never
        : T[K] extends object
        ? Helper<T[K]>
        : T[K];
};

Answer №1

It is essential to have 4 different scenarios considered in this case.

  1. If the data type is an array, then filter out the elements of that array.
  2. If the object contains a fields property, simply utilize the value of T['fields'].
  3. If the value is an object, then repeat the steps recursively for each property that is not 'sys' | 'metadata'.
  4. If none of the above conditions apply (i.e., a primitive value), then exclude the data type.

Proceed with these steps in a recursive manner.

The specified type follows these rules:

type Helper<T> =
  // Map array elements if it is an array
  T extends unknown[] ? Helper<T[number]>[] :

  // Use 'fields' data if it exists
  T extends { fields: unknown } ? Helper<T['fields']> :

  // Clean unwanted keys in object and recurse each property
  T extends object ? { [K in Exclude<keyof T, 'sys' | 'metadata'>]: Helper<T[K]> } :

  // Use the value as is if no other conditions apply
  T

It functions as expected with input data like this:

const obj: Helper<Input> = {
  name: '',
  age: 123,
  father: { name: 'asd' },
  siblings: [{ name: 'qwe' }]
} // works fine

View Playground

Answer №2

Upon removal of the sys and metadata properties, it is advisable to verify whether the initial object had those properties and, if so, intersect the result with the type in that property.

To eliminate those properties, it is recommended to use the method employed by the built-in Omit utility type instead of utilizing never:

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

It seems that a recursive approach may be necessary for your implementation. Though I'm unable to validate the TypeScript code example in your question, the provided solution may be a close match for your requirement, with the possibility of minor adjustments to tailor it to your exact needs.

An illustrative example object has been included at the conclusion to demonstrate the structure resulting from the implementation of the Helper type:

/* Type definitions and example object snippet are provided in the original text. */

TypeScript Playground

If flattening the sys and metadata properties is not necessary for your implementation, you can simplify the corresponding sections for better clarity.

For instance,

T['sys'] extends Object ? Helper<T['sys']> : T['sys']
can be simplified to just T['sys'].

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

Establishing the parameters for a list that is not empty using a specific data type

Is it feasible to establish a non-empty list within TypeScript's type system? While I am aware that it is possible to define a list with a specific number of elements, similar to a Tuple: type TwoElementList = [number, number]; This approach is limi ...

Material UI TreeView: Organize and present node data with multiple columns in a tree structure

const treeItems = [ { id: 1, name: 'English', country: 'US', children: [ { id: 4, name: 'Spring', country: 'Uk', ...

TypeScript: When using an API, it consistently returns an empty object with the type { [key: string]: any }

Every time I try to fetch data from the API, it always comes back empty. See example code snippet below: interface DataStore { [key: string]: any, } static GetData = async (req: Request, res: Response): Promise<Response> => { let obj: Dat ...

"Encountering issues with getStaticPaths not generating any paths

I have a folder named data which contains a file called events.ts: export const EventsData: Event[] = [ { name: 'School-Uniform-Distribution', images: ['/community/conferences/react-foo.png', "/community/conferences/react ...

Troubleshooting problems encountered in Nest.js due to modifications made within a service.ts file

I'm currently working on a Nest.js project and here is the content of the automobile.service.ts file: import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Car } from './enti ...

Transform the code provided by bundleMDX into an HTML string specifically for RSS, all the while utilizing the mdx-bundler

I am currently working on developing an RSS reader and I need to convert the code returned by bundleMDX into a string. This is necessary so that I can utilize it with ReactDOMServer.renderToStaticMarkup(mdx) in my project. You can find a similar implement ...

Creating a type that can be used with a generic type T along with an array of the same generic type T

I am experimenting with TypeScript for this project type ArrayOrSingleType<T> = T | T[]; interface TestType<T> { a: ArrayOrSingleType<T>; b: (v: ArrayOrSingleType<T>) => void; } const testVariable: TestType<number&g ...

How to toggle visibility of multiple div elements in ReactJS

When working in react-js, I encountered a situation where two div elements and two buttons were used. Clicking the first button displayed the first div and hid the second div. Conversely, clicking the second button showed the second div and hid the first d ...

Can Typescript restrict a value to only exist within a specified set of key names within the same object?

I am completely new to Typescript and I am fascinated by the way it can check types. One thing I would like to know is if Typescript can be used to verify at compile time whether a value's domain falls within a predefined set of key names that are de ...

Incorporating responsive design with React and Typescript

Trying to utilize React with TypeScript, I aim to dynamically generate components based on a field name // Storing all available components const components = { ComponentA, ComponentB, }; // Dynamically render the component based on fieldName const di ...

Manage sequential observables and await user input

I have a collection of items that I need to loop through in order to determine whether or not a modal dialog should be displayed to the user, and then pause the iteration until the user provides input. The items are stored within an observable as Observabl ...

Exploring methods for interacting with and controlling structural directives in e2e testing

Background: My goal is to permutation all potential configurations of an Angular2 screen for a specified route and capture screenshots using Protractor from the following link: http://www.protractortest.org/#/debugging. Problem: I am struggling to figure ...

Nest is having trouble resolving dependencies for this service

Can multiple MongoDB models be injected into one resolver and used? I attempted to accomplish this by first adding the import of SectionSchema and SectionsService to the PostsModule: @Module({ imports: [MongooseModule.forFeature([{name: 'Post&apos ...

Using Angular's ElementRef to set focus on an ion-textarea: "The 'setFocus' property is not found on the 'ElementRef' type."

After developing a textarea component that automatically focuses itself when created using the ngAfterViewInit() method, everything seemed to be working perfectly as expected. ngAfterViewInit() { if(this.text.length===0){ this.theinput.setFocus(); ...

MUI is designed to only manage either onBlur or onKeyPress, but not both simultaneously

Currently, I am working on a project with TypeScript and Material-UI. My main goal is to handle both the onBlur event and the onEnter key press event for a TextField component. Here's the scenario: I have incorporated this text field into a menu. Whe ...

Is it possible to utilize instanceof to verify whether a certain variable is of a class constructor type in TypeScript?

I am currently facing an issue with a function that takes a constructor as a parameter and creates an instance based on that constructor. When attempting to check the type of the constructor, I encountered an error. Below are some snippets of code that I ...

Using Angular2, assign a value to the session and retrieve a value from the session

I am having trouble getting and setting a session. Here is my code: login_btnClick() { var NTLoginID = ((document.getElementById("NTLoginID") as HTMLInputElement).value); this._homeService.get(Global.BASE_USER_ENDPOINT + '/EmployeeDe ...

Can we restrict type T to encompass subclasses of K, excluding K itself?

Can a generic type T be restricted to the subset of subtypes of type K, excluding K itself? I am attempting to define a type for inheritance-based mixin functions. An answer for the opposite case is provided in Question 32488309, and interestingly, this qu ...

When navigating using the next and back buttons, the active state in Angular is automatically removed

Looking for some assistance with my quiz app setup. Each question has True/False statements with corresponding buttons to select T or F. However, when I click the next/back button, the active class is not being removed from the previous selection. As a beg ...

The Vue store array declaration triggers a TS error stating that it is not assignable to a parameter of type never

I'm puzzled as to why this error keeps showing up: Argument of type '{ id: string; }' is not assignable to parameter of type 'never'. ... appearing at const index = state.sections.findIndex((section) => section.id === id); T ...