Skip to content

Instantly share code, notes, and snippets.

@eczn
Last active November 19, 2019 08:33
Show Gist options
  • Save eczn/f037be1fa3650304c5c4b80c3b9e18a7 to your computer and use it in GitHub Desktop.
Save eczn/f037be1fa3650304c5c4b80c3b9e18a7 to your computer and use it in GitHub Desktop.
MergeDeep Type
/**
* object skin copy
* e.g: SkinMerge<{ a: 1, c: 1 }, { a: 2, b: 2 }> ==> { a: 2, b: 2, c: 1 }
*/
type SkinMerge<O1, O2> = {
[k in (keyof O1 | keyof O2)]: k extends keyof O2 ? O2[k] : (k extends keyof O1 ? O1[k] : never)
}
/**
* get keys from O where the value is object
* e.g: ObjKeys<{ a: {}, b: {}, c: '1' }> ==> 'a' | 'b'
*/
type ObjKeys<O> = ({
[k in keyof O]: O[k] extends object ? k : never;
})[keyof O];
/**
* contrary to ObjKeys, e.g: NotObjKeys<{ a: {}, b: {}, c: '1' }> ==> 'c'
*/
type NotObjKeys<O> = Exclude<keyof O, ObjKeys<O>>;
/**
* use NotObjKeys<O> to contruct object type from O
* e.g: PickNotObj<{ a: 'a', b: 'b', c: {} }> ==> { a: 'a', b: 'b' }
*/
type PickNotObj<O> = Pick<O, NotObjKeys<O>>;
/**
* Merge Deep For O1 O2
*/
type MergeDeep<O1, O2> = SkinMerge<PickNotObj<O1>, PickNotObj<O2>> & {
// k1 is not in ObjKeys<O2>
[k1 in Exclude<ObjKeys<O1>, ObjKeys<O2>>]: O1[k1]
} & {
// k2 is ObjKeys<O2>, maybe some ObjKeys<k1> alson in there, so we should find it out
[k2 in ObjKeys<O2>]: k2 extends ObjKeys<O1> ? MergeDeep<O1[k2], O2[k2]> : O2[k2]
}
// for test
const t: MergeDeep<
{ a: 'a1', b: { b: 'bbb' }, d: { id: 'fir' } },
{ a: 'a2', c: { ddd: 'ddd123' }, d: { id: 'sec', id2: 'happy' } }
>;
t.a; // 'a2'
t.b.b; // 'bbb'
t.c.ddd; // 'ddd123'
t.d.id; // 'sec'
t.d.id2; // 'happy'
// deep merge
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment