我有3个对象a
,b
和c
,每个对象都有一个prop
字段,其值可以是任何值。
const a = { prop: { foo: 'bar', bar: true } }
const b = { prop: { baz: 1234 } }
const c = { prop: true }
这些对象分为几部分:
const sectionA = { a, b }
const sectionB = { c }
const allSections = {sectionA, sectionB}
我想动态构造以下对象:
const myObject = {
sectionA: {
a: {foo: 'bar', bar: true},
b: {baz: 1234}
},
sectionB: {
c: true
}
}
即正确地组织成嵌套对象,但没有prop
字段。
这就是我所做的:
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>)
在行const objectName = _objectName as keyof typeof section;
上,objectName
不幸地是never
(逻辑上是因为对象之间没有公共字段)。但是后来我做不到innerAcc[objectName]
。
我该如何解决?
答案 0 :(得分:0)
注意:以下内容在TS3.4 +中有效,因为它依赖于higher order type inference from generic functions。
这就是我要做的。这是一个相当大的重构,主要是因为所有嵌套类型都伤害了我的大脑。您可能可以将类型从其中复制到原始代码中,但是我不会尝试这样做。但是,实际行为几乎是相同的(例如,使用Object.keys().reduce()
而不是for
循环):
// 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
您可以验证result
是您期望的类型。这里的技巧基本上是使pullOutProp()
和nestedPullOutProp()
尽可能通用,对具有最低功能要求的类型进行操作(例如,T extends { [K in keyof T]: Record<keyof T[K], { prop: any }> }>
意味着将存在t.x.y.prop
只要t.x.y
存在),然后使对reduce()
的回调通用。在每一步骤中,泛型类型都足够直接让编译器遵循逻辑,而不必抱怨分配问题……尤其是因为对TS3.4中引入的泛型函数中的高阶类型推断进行了改进。
仅在最后,当您调用nestedPullOutProp(allSections)
时,编译器实际上会继续尝试评估泛型,此时泛型将成为预期的类型。
好的,希望能有所帮助;祝你好运!