Syler
Syler

Reputation: 250

Deep Replace Object type with Other Object Type if key is present

How can i replace all properties of B with the values of A? and omit all keys of A that are not in B.

type A = {
  a: {
    a: string
    b: number
    c: boolean
    d: {
      a: any
      b: any
    }
  }
  b: { b: null }

}
type B = {
  a: {
    a: boolean
    b: boolean
    d: { a: boolean }
  }
}
// Result
type C = {
  a: {
    a: string
    b: number
    d: { a: any }
  }
}

Edit:

Here is an example why @catgirlkelly answer doesn't work in my case.

type DeepReplace<T, U> = T extends object ? U extends object ?
  { [K in keyof T]: K extends keyof U ? DeepReplace<T[K], U[K]> : T[K] } : U : U


type Override<Source, Target> = Source extends object ? {
  [K in keyof Source]: K extends keyof Target ? Override<Source[K], Target[K]> : Source[K];
} : Target;


type QueryObj<T> = T extends object ? {
  [Key in keyof T]?: QueryObj<T[Key]>;
} : NonNullable<T> extends object ? never : boolean;


type GRAPH_QL_TYPE = {
  id: string,
  name: string,
  Assets?: {
    items: ({
      id: string,
      name: string,
    } )[],
  } ,
};


function query<Query extends QueryObj<GRAPH_QL_TYPE>>(query: Query) {
  // Doesn't work
  return {} as Override<Query, GRAPH_QL_TYPE>
  // Works
  // return {} as DeepReplace<Query, GRAPH_QL_TYPE>
}

const res = query({
  name: true,
  id: true,
  Assets: {
    items: [
      {
        name: true,
        id: true,
      }
    ]
  }
})
// res.Assets.items[0].name = true with Override, and it equals string with DeepReplace
const reactState: GRAPH_QL_TYPE = res

Upvotes: 3

Views: 1048

Answers (1)

jcalz
jcalz

Reputation: 329398

These sorts of deep object type transformations tend to have lots of edge cases (like optional properties, index signatures, and union types) so anyone who comes along with the "same" question should take care to test any answers very carefully. Here's one possible approach:

type DeepReplace<T, U> =
  T extends object ? U extends object ? {
    [K in keyof T]: K extends keyof U ? DeepReplace<T[K], U[K]> : T[K]
  } : U : U

DeepReplace<T, U> recursively replaces (pieces of) T with (pieces of) U.

If either T or U are not objects, then U is returned as-is. This is a judgment call and a probable edge case: when one of T or U is an object and the other is not, it's not 100% clear what the "right" behavior is.

Otherwise we map over the properties of T so that any property key K that's also found in U will cause the property of T to be DeepReplaced with the corresponding property from U. This is the "core" of DeepReplace<T, U> and is probably going to be found in most implementations.

If there are any property keys of T that are not found in U, that property just stays the same and is not replaced. This is another judgment call and a probable edge case as well.


Anyway, let's test it on your example code:

type C = DeepReplace<B, A>
/* type C = {
    a: {
        a: string;
        b: number;
        d: {
            a: any;
        };
    };
} */

So this works as desired, hooray! And apparently it also works for your other use case:

type Query = {
  name: true;
  id: true;
  Assets: {
    items: {
      name: true;
      id: true;
    }[];
  };
};

type GraphQLType = {
  id: string,
  name: string,
  Assets?: {
    items: ({
      id: string,
      name: string,
    })[],
  },
};

type QueryResult = DeepReplace<Query, GraphQLType>
/* type QueryResult = {
    name: string;
    id: string;
    Assets: {
        items: {
            name: string;
            id: string;
        }[];
    } | undefined;
} */

Double hooray. The exact desired types around optional properties and unions with undefined are not 100% obvious to me, but again, this depends on the particulars of the use cases.

Playground link to code

Upvotes: 1

Related Questions