admin管理员组文章数量:1122832
Given a type Type
who takes a record with a tuple including a union of strings and one string of that union (think of it as options and a default), and takes an union of string build such as each string match this pattern ${key of first type}:${one of the value in first type union}
type BuildPart<T extends Record<string, [string, string]>> = '' | {
[K in keyof T]: K extends string ? `${K & string}:${T[K][number]}` : never
}[keyof T]
type Type<All extends Record<string, [string, string]>, Part extends BuildPart<All> = ''> = {
[K in keyof All]: K extends string ? Part extends `${K}:${infer V}` ? V : All[K][1] : never
}[keyof All]
What i want to do is to retrieve values picked, ie either the value set as the original default or if one was provided by Part
then pick that one.
type test0 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}> // 'c' | 'd'
type test1 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b'> // 'b' | 'd
type test2 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b' | 'b:e'> // 'b' | 'c' | 'd' | 'e' but expected 'b' | 'e'
It works as expected with Part
empty or holding a single value, but as soon as Part
is an union then all possible values are rendered
playground
Given a type Type
who takes a record with a tuple including a union of strings and one string of that union (think of it as options and a default), and takes an union of string build such as each string match this pattern ${key of first type}:${one of the value in first type union}
type BuildPart<T extends Record<string, [string, string]>> = '' | {
[K in keyof T]: K extends string ? `${K & string}:${T[K][number]}` : never
}[keyof T]
type Type<All extends Record<string, [string, string]>, Part extends BuildPart<All> = ''> = {
[K in keyof All]: K extends string ? Part extends `${K}:${infer V}` ? V : All[K][1] : never
}[keyof All]
What i want to do is to retrieve values picked, ie either the value set as the original default or if one was provided by Part
then pick that one.
type test0 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}> // 'c' | 'd'
type test1 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b'> // 'b' | 'd
type test2 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b' | 'b:e'> // 'b' | 'c' | 'd' | 'e' but expected 'b' | 'e'
It works as expected with Part
empty or holding a single value, but as soon as Part
is an union then all possible values are rendered
playground
Share Improve this question asked Nov 22, 2024 at 15:47 zedryaszedryas 97411 silver badges19 bronze badges 3 |1 Answer
Reset to default 1The problem is that
Part extends `${K}:${infer V}` ? V : All[K][1]
is a distributive conditional type and acts on all union members of Part
separately. If any union member of Part
fails to match K
, then you'll get the default for K
in the response (All[K][1]
). Like, using your example above, when K
is "a"
and Part
is "a:b" | "b:e"
, then first it evaluates "a:b" extends `a:${infer V}` ? V : "c"
which takes the true branch and gives you "b"
, and then it evaluates "b:e" extends `a:${infer V}` ? V : "c"
which takes the false branch and gives you "c"
. So you get "b" | "c"
, which you don't want.
We cannot have that All[K][1]
in the false branch. Instead it needs to be never
, so that the whole thing evaluates to just "b"
. Only in the case that the entire conditional type is never
do we select "c"
. I'd rather break this out into its own utility type like
type GetSuffix<K extends string, P extends string> =
P extends `${K}:${infer V}` ? V : never;
So for each K
we want GetSuffix<K, P>
for the entire union P
, unless that's never
, in which case we want All[K][1]
. The "use this unless it's never
, in which case use this other thing" can also be broken out to a utility type
type OrDefault<T, D> = [T] extends [never] ? D : T;
(and we really don't want that to be distributive). That makes Type
look like
type Type<O extends Record<string, [string, string]>, P extends BuildPart<O> = never> = {
[K in string & keyof O]: OrDefault<GetSuffix<K, P>, O[K][1]>
}[string & keyof O]
which is very similar to yours (although I've moved where we check for K
being string
around). Let's try it:
type test0 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }>
// ^? type test0 = 'c' | 'd'
type test1 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }, 'a:b'>
// ^? type test1 = 'b' | 'd'
type test2 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }, 'a:b' | 'b:e'>
// ^? type test2 = 'b' | 'e'
Looks good.
Playground link to code
本文标签: typescriptUnion breaks extraction of values in an record of stringsStack Overflow
版权声明:本文标题:typescript - Union breaks extraction of values in an record of strings - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736302605a1931595.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
Type
? Or to debug yours? – jcalz Commented Nov 22, 2024 at 17:06Part
insideType
, or at least you can't have the false branch give youAll[K][1]
. Instead you should probably factor out the "find the suffix in a union" to a new utility type, such as shown in this playground link. Does that fully address the question? If so I'll write an answer; if not, what's missing? – jcalz Commented Nov 22, 2024 at 17:16