Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change type of values in deeply nested object structure

I need to recursively get through the data structure and create a type that has some of the fields changed to a different type, based on a condition.

Based on the following structure, I need to create a type (Result) where all A types are replaced with B types.

class A{}
class B{}

const data = {
    propA:new AA,
    propB:true,
    nested:{
        propC:new AA,
        propD:false
    }
}

// something like:
type Replace<typeof data, A, B>

// result
type Result = {
    propA:B,
    propB:boolean,
    nested:{
        propC:B
        propD:boolean
    }

}
like image 937
Ivan V. Avatar asked Oct 24 '25 21:10

Ivan V.


2 Answers

You can do this with a mapped type, but keep in mind that matching is based on object structure rather than class name, so an object from a class C{} will also get converted when you're targeting A.

The Replace type can be defined as

type Replace<T, From, To> = T extends (...args: any[]) => any ? T : {
  [K in keyof T]: 
    [T[K], From] extends [From, T[K]] ? To : Replace<T[K], From, To>
}

The first condition is to preserve any methods/function properties as mapped types will convert these to {}. The mapped type itself processes each key, and checks whether both value extends the From type and the From type extends the value type, to ensure equality. If both are equal, the value gets replaced with To, and otherwise Replace is called recursively.

Here's an example conversion:

class A{}
class B{b = 42}
class C{}

const data = {
    propA: new A(),
    propBool: true,
    propC: new C(),
    nested:{
        propA: new A(),
        propBool: false
    },
    f: () => 42
}

type Result = Replace<typeof data, A, B>
// type Result = {
//     propA: B;
//     propBool: boolean;
//     propC: B;
//     nested: {
//         propA: B;
//         propBool: boolean;
//     };
//     f: () => number;
// }

TypeScript playground

like image 69
Oblosys Avatar answered Oct 26 '25 18:10

Oblosys


This can be achieved with conditional, recursive types:

class A {
    ClassPropA!: string
}
class B {
    ClassPropB!: string
}

const data = {
    propA: new A(),
    propB: true,
    nested:{
        propC: new A(),
        propD:false
    }
}

type Replace<T, A, B> = T extends object
    ? { [key in keyof T]: T[key] extends A ? B : Replace<T[key], A, B> }
    : T;

// type Result = {
//     propA: B,
//     propB: boolean,
//     nested:{
//         propC: B
//         propD: boolean
//     }
//
// }
type Result = Replace<typeof data, A, B>;
like image 30
Joey Kilpatrick Avatar answered Oct 26 '25 19:10

Joey Kilpatrick