简体   繁体   English

从 object 个键中检索推断的字符串文字

[英]Retrieve inferred string literal from object keys

I'm trying to create a type that defines the value based on the key.我正在尝试创建一个基于键定义值的类型。 If the key extends $${string} (eg $foo ) the value should be the key without the prefix eg foo .如果键扩展$${string} (例如$foo ),则该值应该是不带前缀的键,例如foo If the key doens't extend $${string} (eg boo ) the values should be null .如果密钥不扩展$${string} (例如boo ),则值应为null

Example例子

const example = {
  $foo: 'foo',
  boo: null,
}

Here is an isolated example I created to get it done - but it doesn't work as intended when I apply it to the code below.这是我为完成它而创建的一个独立示例 - 但是当我将它应用于下面的代码时它并没有按预期工作。

type Value<T> = T extends `$${infer I}` ? I : null

type ExampleA = Value<'$foo'> // type is 'foo' 
type ExampleB = Value<'boo'>  // type is null

My current code我当前的代码

type Values = {
    [K in string]: K extends `$${infer p}` ? p : null;
}

const values = {
    $foo: 'foo', // Type 'string' is not assignable to type 'null'.
    foo: null,
    
    $boo: 'boo', // Type 'string' is not assignable to type 'null'.
    boo: null,
} satisfies Values;

type Expected = {
    readonly $foo: 'foo',
    readonly foo: null,
    
    readonly $boo: 'boo',
    readonly boo: null,
}

The satisfies Values is used to infer the type later on. satisfies Values用于稍后推断类型。 Similar approach is acceptable类似的方法是可以接受的

Thanks for your help and time - cheers感谢您的帮助和时间 - 干杯

The problem with your Values type is that mapped types over string do not behave the way you expect them to.您的Values类型的问题在于, string上的映射类型不会按照您期望的方式运行。 While conceptually you can think of string as the infinite union of all possible string literal types , a mapped type over string does not even try to iterate over every possible string literal type;虽然从概念上讲,您可以将string视为所有可能的字符串文字类型的无限联合,但字符串上的映射类型甚至不会尝试遍历所有可能的string文字类型; it just maps one thing: string :它只映射一件事: string

type Values = {
  [K in string]: K extends `\$${infer S}` ? S : null;
}
/* type Values = {
    [x: string]: null;
} */

And since string does not extend `\$${infer S}` , then the property type for the string key is null .由于string不扩展`\$${infer S}` ,因此string键的属性类型为null

This is working as intended, as discussed in microsoft/TypeScript#22509 .正如microsoft/TypeScript#22509中所讨论的,这是按预期工作的。 Mapped types over string are not what you want. string上的映射类型不是您想要的。


And unfortunately there is no way to write a specific type in TypeScript which behaves the way you want.不幸的是,没有办法在 TypeScript 中编写一个特定类型,它的行为符合您的要求。 The closest you could get is something like你能得到的最接近的是

type Values = {
  [k: `\$${string}`]: string;
  [k: string]: string | null;
}

using a template string pattern index signature , but the parts where the property value string needs to match the part after the "$" character (not just string ) and the part where other keys need to have a null (not just string | null ) cannot be represented:使用模板字符串模式索引签名,但属性值字符串需要匹配"$"字符后的部分(不仅仅是string )和其他键需要具有null (不仅仅是string | null )的部分不能代表:

const values = {
  $foo: 'foo',
  foo: null,
  $boo: 'boo',
  boo: null,
  $oops: null, // error, not string
  oops: 'hmm', // should be error, but isn't!
  $whoops: 'oops', // should be error, but isn't!
} satisfies Values;

So we have to give up on the approach using the satisfies operator , because there is no appropriate Values type to use it with.所以我们不得不放弃使用satisfies运算符的方法,因为没有合适的Values类型来使用它。


What you really care about is having the type of values inferred by the compiler but still checked against your desired constraint.您真正关心的是编译器推断的values类型,但仍会根据您想要的约束进行检查。 We can get behavior like this by replacing satisfies Values with a generic helper function we can call satisfiesValues() .我们可以通过用通用助手 function 替换satisfies Values来获得这样的行为,我们可以调用satisfiesValues() At runtime this function just returns its input, but the compiler can use it to validate the object literal passed in. So instead of const values = {...} satisfies Values;在运行时,这个 function 只返回它的输入,但是编译器可以使用它来验证传入的 object 文字。所以不是const values = {...} satisfies Values; you would write const values = satisfiesValues({...});你会写const values = satisfiesValues({...}); . .

Here's one possible implementation:这是一种可能的实现:

const satisfiesValues = <K extends PropertyKey>(
  val: { [P in K]: P extends `\$${infer S}` ? S : null }
) => val;

The function is generic in K , the keys of the val value passed in. This will most likely be some union of known keys (none of which will be just string ), and then the mapped type behaves as desired: function 在K中是通用的,传入的val值的键。这很可能是已知键的一些联合(没有一个只是string ),然后映射类型的行为符合预期:

const values = satisfiesValues({
  $foo: 'foo',
  foo: null,
  $boo: 'boo',
  boo: null,
  $oops: null, // error, not "oops"
  oops: 'hmm', // error, not null
  $whoops: 'oops', // error, not "whoops"
});

/* const values: {
    foo: null;
    $foo: "foo";
    boo: null;
    $boo: "boo";
    oops: null;
    $whoops: "whoops";
    $oops: "oops";
} */

Looks good.看起来挺好的。 The type of values is what you want it to be, and the compiler allows the valid properties and complains about the invalid ones. values的类型是你想要的,编译器允许有效的属性并抱怨无效的属性。

Playground link to code 游乐场代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM