typescript - Tip pentru extragerea cheilor obiectelor încorporate
Să spunem că nu am niciun tip pentru o parte a datelor și structura de date imbricată care arată astfel:
interface Element {
foo: string;
bar: string;
}
interface Elements {
[name: string]: Partial<Element>;
}
interface Scopes {
main: Elements;
[other: string]: Elements;
}
Apoi am o funcție care primește un obiect de tip Scopes și, de asemenea, un parametru care definește ce scope-uri ar trebui să le combin în obiectul de ieșire (acesta va fi aplatizat fără scope-uri). Funcția ar putea arăta astfel:
function merge<S extends Scopes>(obj: S, selectors?: { [K in keyof S]?: boolean }): UNKNOWNTYPE {
// this will be some logic to extract and merge scopes
}
Am o problemă în a defini corect tipul de ieșire pentru această funcție UNKNOWNTYPE. Vreau să fac dinamic și să extrag cheile din obiectul de intrare, astfel încât pentru următoarele date de intrare să obțin această ieșire:
const scopes: Scopes = {
main : {
a : {
foo: "a"
},
b : {
bar: "b"
}
},
other : {
b : {
foo : "b"
},
c : {
foo : "c"
}
}
}
const out = merge(scopes, { main : true, other : true });
/*
out is of this strcture
{
a : {
foo : "a"
},
b : {
foo : "b"
bar : "b"
},
c : {
foo : "c"
}
}
*/
Am încercat unele abordări și am încercat acest tip, dar nu rezolvă toate cheile domeniilor combinate:
interface UNKNOWNTYPE<Scopes, Scope extends keyof Scopes = keyof Scopes> {
[K in keyof Scopes[Scope]: Element]
}
Există vreo modalitate de a aplatiza dinamic și de a intersecta un obiect de nivel 2 dintr-un obiect imbricat?
Răspuns #1
Ok, îmi place acest tip de lucru. În primul rând: nu este chiar posibil să faci asta în general; ar trebui să poți enumera cheile la nivel de tip, iar TypeScript pur și simplu nu este acolo (încă sau poate niciodată). În măsura în care poți să o faci, trebuie să ții de mână sistemul de tipuri, deoarece este puțin probabil să poată deduce singur ce vrei să facă. Asta fiind spus, iată o posibilă cale de urmat:
Supraîncărcați funcția de câte ori vă așteptați la numărul de chei din scopes:
function merge<S extends Scopes, KA extends keyof S>(obj: S, selectors?: {[K in KA]?: boolean}): S[KA];
function merge<S extends Scopes, KA extends keyof S, KB extends keyof S>(obj: S, selectors?: {[K in KA | KB]?: boolean}): S[KA] & S[KB];
function merge<S extends Scopes, KA extends keyof S, KB extends keyof S, KC extends keyof S>(obj: S, selectors?: {[K in KA | KB | KC]?: boolean}): S[KA] & S[KB] & S[KC];
//... merge with 4, 5, 6, etc
function merge<S extends Scopes>(obj: S, selectors?: { [K in keyof S]?: boolean }): Elements {
// this will be some logic to extract and merge scopes
return null as any; // for now
}
Apoi, când declari scopes, probabil că nu ar trebui să declari tipul ca fiind Scopes, altfel sistemul de tipuri va uita de cheile particulare pe care le setezi. Dacă doar lași sistemul să infereze un tip, va ști toate cheile și tipurile valorilor:
const scopes = {
main : {
a : {
foo: "a"
},
b : {
bar: "b"
}
},
other : {
b : {
foo : "b"
},
c : {
foo : "c"
}
}
}
În cele din urmă, sunteți gata să apelați merge(). Trebuie să specificați explicit tipurile generice. Primul este tipul variabilei scopes cu toate cheile și valorile care vă interesează (care este doar typeof scopes deoarece nu l-am setat la Scopes), iar următoarele sunt cheile individuale din selectors:
const out = merge<typeof scopes, 'main','other'>(scopes, { main: true, other: true });
out.a.foo;
out.a.bar; // error as expected
out.b.foo;
out.b.bar;
out.c.foo;
out.c.bar; // error as expected
Și obții un out exact de tipul pe care îl dorești. Merită toate aceste ocoliri? Depinde de tine. Mult noroc!
