递归转换typescript中对象树的所有叶子
给定一个简单的对象树,该对象树包含其自身类型的值或需要转换的类型的值:递归转换typescript中对象树的所有叶子,typescript,tree,Typescript,Tree,给定一个简单的对象树,该对象树包含其自身类型的值或需要转换的类型的值: 接口树{ [键:字符串]:树|叶; } 我想定义一个函数,递归地将所有叶子转换为另一种类型,如下所示: const t1 = { a: "A", b: { c: "CC", d: { e: "EEE" } } }; const t2 = transformTree(t1, (x: string) => x.length, (v): v is string => typeof v === "str
接口树{
[键:字符串]:树|叶;
}
我想定义一个函数,递归地将所有叶子转换为另一种类型,如下所示:
const t1 = { a: "A", b: { c: "CC", d: { e: "EEE" } } };
const t2 = transformTree(t1,
(x: string) => x.length,
(v): v is string => typeof v === "string"
);
t2.a; // number
t2.b.c; // number
t2.b.d.e; // number
type TransformTreeIdx<T, X, K extends keyof X> = { [P in keyof T]:
T[P] extends X ? X[K] :
TransformTreeIdx<T[P], X, K>;
};
declare function transformTreeIdx<TX, X, K extends keyof X>(
obj: TX,
key: K,
isLeaf: (value: any) => value is X
): TransformTreeIdx<TX, X, K>;
函数转换树(
obj:树,
变换:(值:S)=>T,
isLeaf:(值:S |树)=>布尔值
):树{
返回Object.assign(
{},
…Object.entries(obj.map)([key,value])=>({
[键]:isLeaf(值)
?转换(值为S)
:transformTree(值为Tree、transform、isLeaf),
}))
);
}
如何维护源树和转换树之间的叶子类型
测试上述内容不起作用:
类包装器{
构造函数(公共值:T){}
}
函数转换(包装:包装):T{
返回值;
}
函数展开(包装:树):树{
返回转换树(
包裹,
使改变
(值:Wrapper | Tree)=>Wrapper的值实例
);
}
const obj=展开({
傅:{
酒吧:新包装(“baz”),
},
奶牛:新包装(“moo”),
});
功能把手(值:“baz”){
返回true;
}
函数handleMoo(值:“moo”){
返回true;
}
把手(obj.foo.bar);//错误:(162,19)TS2339:类型“string | Tree”上不存在属性“bar”。类型“string”上不存在属性“bar”。
handleMoo(对象为奶牛)//错误:(163,11)TS2345:类型为'string | Tree'的参数不能分配给类型为'moo'的参数。类型“string”不可分配给类型“moo”。
我可以看到出现了错误,因为树结构不是通过转换来维护的(转换只在运行时起作用)。但是考虑到输入树的已知结构,转换是可预测的,我觉得应该有一种方法在typescript中实现这一点
以下是我试图解决的问题:
类型转换树={
[K in keyof InputTree]:InputTree[K]扩展叶
T
:InputTree[K]扩展树
?转换树
:从不;
};
类型IsLeaf=(值:树|叶)=>布尔值;
函数转换树(
树:T,
转换:(值:从)=>到,
isLeaf:isLeaf
):转换树{
返回Object.assign(
{},
…Object.entries(tree.map)([key,value])=>({
//XXX必须在每种情况下转换值,因为typescript无法预测
//isLeaf()的结果。
[键]:isLeaf(值)
?转换(提取值)
:转换树(
作为提取物的价值,
使改变
岛屿
),
}))
);
}
它似乎仍然不理解嵌套类型:
函数展开(
包装:树
):转换树{
返回转换树(
包裹,
使改变
(值:Wrapper | Tree)=>Wrapper的值实例
);
}
功能把手(值:“baz”){
返回true;
}
函数handleMoo(值:“moo”){
返回true;
}
handleMoo(对象为奶牛);//好啊
把手(obj.foo.bar);//TS2339:类型“从不”上不存在属性“bar”。
似乎typescript仍然认为,如果字段值不是叶子,那么它可能不是子树。在下面的内容中,我只关心键入而不是实现。所有函数都将是
declare
d,就好像实际的实现在某个JS库中,而这些就是它们的实现一样
另外,isLeaf
函数的类型可能应该是返回类型为类型谓词的函数,而不仅仅是boolean
。函数签名isLeaf:(value:S | Tree)=>value is S
与返回boolean
的函数签名类似,只是编译器实际上会理解在if(isLeaf(x)){x}else{x}
中,真块中的x
将是S
,假块中的x
将是树
好的,下面是:
类型
树
过于笼统,无法跟踪特定的键和值类型。编译器只知道该类型的值,比如说,Tree
,它是一个对象类型,其属性是类型string
或Tree
。一旦你这样做了,比如说:
const x: Tree<string> = { a: "", b: { c: "", d: { e: "" } } };
如果您所关心的只是提出一种类型转换,这种转换可以维护嵌套的键结构,并将树的某个子类型转换为具有相同形状的树的子类型,那么您可以这样做。但在最直接的实现中,生成的树的所有叶子都将是Y
类型,而不是更窄的类型。我是这样写的:
type TransformTree<T extends Tree<X>, X, Y> = { [K in keyof T]:
T[K] extends X ? Y :
T[K] extends Tree<X> ? TransformTree<T[K], X, Y> :
T[K];
};
declare function transformTree<X, Y, TX extends Tree<X>>(
obj: TX,
transform: (value: X) => Y,
isLeaf: (value: X | Tree<X>) => value is X
): TransformTree<TX, X, Y>;
但你希望这里有更雄心勃勃的东西;似乎您不仅希望叶变换映射从特定类型X
到特定类型Y
,还希望指定一些常规类型函数,如type F=…
,并将叶从类型Z扩展X
映射到F
在您的示例中,您的输入类型类似于Wrapped,虽然拥有它们会让人惊讶,但在可预见的未来似乎不会发生
您可以做的是设想特定类型的叶类型转换,并为它们实现特定版本的TransformTree
。例如,如果叶类型映射只是索引到单个属性中,如type F=T[K]
,如Unwrap
中所示,则可以这样编写:
const t1 = { a: "A", b: { c: "CC", d: { e: "EEE" } } };
const t2 = transformTree(t1,
(x: string) => x.length,
(v): v is string => typeof v === "string"
);
t2.a; // number
t2.b.c; // number
t2.b.d.e; // number
type TransformTreeIdx<T, X, K extends keyof X> = { [P in keyof T]:
T[P] extends X ? X[K] :
TransformTreeIdx<T[P], X, K>;
};
declare function transformTreeIdx<TX, X, K extends keyof X>(
obj: TX,
key: K,
isLeaf: (value: any) => value is X
): TransformTreeIdx<TX, X, K>;
并使用它:
const u = {
foo: {
bar: "baz" as const,
},
cow: "moo" as const,
};
const u2 = transformTreeWrap(
u, (x: any): x is string => typeof x === "string"
);
handleBaz(u2.foo.bar.value);
handleMoo(u2.cow.value);
或者,您可能有一个isLeaf
/transform
对数组,允许针对不同的更具体的转换测试每个节点。因此,例如,每当您在树中找到一个“moo”
值时,您就会输出一个数字
,每当您找到一个“baz”
值时
const u = {
foo: {
bar: "baz" as const,
},
cow: "moo" as const,
};
const u2 = transformTreeWrap(
u, (x: any): x is string => typeof x === "string"
);
handleBaz(u2.foo.bar.value);
handleMoo(u2.cow.value);
type TransformTreeMap<T, M extends [any, any]> = { [K in keyof T]:
T[K] extends M[0] ? Extract<M, [T[K], any]>[1] :
TransformTreeMap<T[K], M> };
type IsLeafAndTransformer<I, O> = {
isLeaf: (x: any) => x is I,
transform: (x: I) => O
}
type TransformArrayToMap<M extends Array<IsLeafAndTransformer<any, any>>> = {
[K in keyof M]: M[K] extends IsLeafAndTransformer<infer I, infer O> ?
[I, O] : never }[number]
declare function transformTreeMap<T, M extends Array<IsLeafAndTransformer<any, any>>>(
obj: T,
...transformers: M
): TransformTreeMap<T, TransformArrayToMap<M>>;
const mm = transformTreeMap(u,
{ isLeaf: (x: any): x is "moo" => x === "moo", transform: (x: "moo") => 123 },
{ isLeaf: (x: any): x is "baz" => x === "baz", transform: (x: "baz") => true }
);
mm.cow // number
mm.foo.bar // boolean
function leafsToString<O extends Record<any, any>>(obj: O): unknown {
const newObj = obj instanceof Array ? [] : Object.create(null);
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object') {
newObj[key] = leafsToString(obj[key]);
} else {
newObj[key] = obj[key] + '';
}
}
return newObj;
}
type LeafsToString<O> = {
[K in keyof O]:
O[K] extends Record<any, any>
? LeafsToString<O[K]>
: string
}