Reputation: 250
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
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 DeepReplace
d 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.
Upvotes: 1