typescript - Object.keys() s ugniježđenim objektima: dobivanje ispravnih tipova
Imam 3 objekta a, b i c, od kojih svaki ima polje prop, čija vrijednost može biti bilo što.
const a = { prop: { foo: 'bar', bar: true } }
const b = { prop: { baz: 1234 } }
const c = { prop: true }
Ovi su objekti organizirani u odjeljke:
const sectionA = { a, b }
const sectionB = { c }
const allSections = {sectionA, sectionB}
Želim dinamički konstruirati sljedeći objekt:
const myObject = {
sectionA: {
a: {foo: 'bar', bar: true},
b: {baz: 1234}
},
sectionB: {
c: true
}
}
To jest, ispravno organizirano u ugniježđene objekte, ali bez polja prop.
Evo što sam učinio:
type ObjectWithProp = { prop: any }
type ObjectTypes<Section extends {[index:string]: ObjectWithProp}> = {
[K in keyof Section]: Section[K]['prop']
}
type AllSectionTypes<AllSection extends { [index: string]: { [index: string]: ObjectWithProp } }> = {
[K in keyof AllSection]: ObjectTypes<AllSection[K]>
}
const result = Object.keys(allSections).reduce((outerAcc, _key) => {
const key = _key as keyof typeof allSections;
const section = allSections[key];
outerAcc[key] = Object.keys(section).reduce((innerAcc, _objectName) => {
const objectName = _objectName as keyof typeof section;
const obj = section[_objectName];
innerAcc[objectName] = obj.prop;
return innerAcc;
}, {} as ObjectTypes<typeof section>);
return outerAcc;
}, {} as AllSectionTypes<typeof allSections>)
[Veza na TS Playground]
Na liniji const objectName=_objectName as keyof typeof section;, objectName je nažalost never (logično jer ne postoje zajednička polja između objekata). Ali tada ne mogu napraviti innerAcc[objectName].
Kako to mogu riješiti?
Odgovor #1
Napomena: sljedeće funkcionira u TS3.4+ jer se oslanja na izvedbu tipova višeg reda iz generičkih funkcija.
Evo kako bih ja to napravio. To je prilično veliko preuređivanje koda, uglavnom zato što su mi svi ugniježđeni tipovi stvarali glavobolju. Vjerojatno možete kopirati tipove iz ovog primjera u svoj originalni kod, ali ja ne bih pokušavao to učiniti. Sama funkcionalnost, međutim, je otprilike ista (npr., korištenje Object.keys().reduce() umjesto for petlje):
// a technically unsafe version of Object.keys(o) that assumes that
// o only has known properties of T
function keys<T extends object>(o: T) {
return Object.keys(o) as Array<keyof T>;
}
// Turn {k1: {prop: v2}, k3: {prop: v4} into {k1: v2, k3: v4}
function pullOutProp<TK extends Record<keyof TK, { prop: any }>>(o: TK) {
return keys(o).reduce(
<P extends keyof TK>(
acc: { [P in keyof TK]: TK[P]['prop'] },
k: P
) => (acc[k] = o[k].prop, acc),
{} as { [P in keyof TK]: TK[P]['prop'] });
}
// Turn {k1: {k2: {prop: v3}, k4: {prop: v5}}, k6: {k7: {prop: v8}}} into
// {k1: {k2: v3, k4: v5}, k6: {k7: v8}}
function nestedPullOutProp<T extends {
[K in keyof T]: Record<keyof T[K], { prop: any }> }
>(o: T) {
return keys(o).reduce(
<K extends keyof T>(
acc: { [K in keyof T]: { [P in keyof T[K]]: T[K][P]['prop'] } },
k: K
) => (acc[k] = pullOutProp(o[k]), acc),
{} as { [K in keyof T]: { [P in keyof T[K]]: T[K][P]['prop'] } }
)
}
const result = nestedPullOutProp(allSections); // your desired result
Možete provjeriti da je result tip koji očekujete. Trik ovdje je u osnovi učiniti pullOutProp() i nestedPullOutProp() što generičkijima, radeći nad tipom s minimalnim zahtjevima da funkcioniraju (npr., T extends { [K in keyof T]: Record<keyof T[K], { prop: any }> }> znači da će t.x.y.prop postojati kad god t.x.y postoji), i učiniti callback za reduce() generičkim. Na svakom koraku generički tipovi su dovoljno jednostavni da kompilator može pratiti logiku i ne žali se zbog dodjela... posebno zbog poboljšanja uvođenja inferencije višerazinskih tipova u generičkim funkcijama koje su uvedene u TS3.4.
Tek na samom kraju, kada pozovete nestedPullOutProp(allSections), kompajler zapravo nastavlja i pokušava procijeniti generike, u kojem trenutku on postaje očekivani tip.
U redu, nadam se da pomaže; sretno!
Veza na kod
