简体   繁体   中英

Typescript constrain generic to string literal type for use in computed object property

I'm trying to write a function which will take a string literal and return an object with a single field whose name is that string literal. I can write a function that does what I want, but I don't know how to express the constraint that its argument type be a string literal.

The closest I've got is using a generic type which extends string . This permits string literal types, but also unions of string literal types and the type string , which I don't want to be passed to my function.

This compiles, and does what I want, provided that K is a string literal type. Note that type assertion wasn't necessary in typescript 3.4 but it is required in 3.5.

function makeObject<K extends string>(key: K): { [P in K]: string } {
  return { [key]: "Hello, World!" } as { [P in K]: string };
}

If K is anything other than a string literal, the return type of this function won't be the same as the type of the value it returns.

The 2 avenues I can imagine for making this work are:

  • constrain K to be only string literals
  • express that the return type be an object with a single field whose name is a value in K (less satisfying, but at least the type of the function will be honest)

Can typescript's type system express either of these?

If I remove the type assertion in typescript 3.5 I get the error:

a.ts:2:3 - error TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

2   return { [key]: "Hello, World!" };

There is no constraint for something to be a single string literal type. If you specify extends string the compiler will infer string literal types for K but it will also by definition allow unions of string literal types (after all the set of a union of string literal types is included in the set of all strings)

We can create a custom error, that forces as call to be in an error state if it detects a union of string literal types. Such a check can be done using conditional types making sure that K is the same as UnionToIntersection<K> . If this is true K is not a union, since 'a' extends 'a' but 'a' | 'b' 'a' | 'b' does not extends 'a' & 'b'

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type CheckForUnion<T, TErr, TOk> = [T] extends [UnionToIntersection<T>] ? TOk : TErr

function makeObject<K extends string>(key: K & CheckForUnion<K, never, {}>): { [P in K]: string } {
    return { [key]: "Hello, World!" } as { [P in K]: string };
}

makeObject("a")
makeObject("a" as "a" | "b") // Argument of type '"a" | "b"' is not assignable to parameter of type 'never'

Update for TypeScript 4.2

The following works:

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

(No longer works): TypeScript 4.1 template literal type trick

Edit: The below actually broke in 4.2. Discussion here

type StringLiteral<T> = T extends `${string & T}` ? T : never;

TS 4.1 introduced template literal types which allows you to convert string literals to other string literals. You can convert the string literal to itself. Since only literals can be templated and not general strings, you just then conditionally check that the string literal extends from itself.

Full example:

type StringLiteral<T> = T extends `${string & T}` ? T : never;

type CheckLiteral = StringLiteral<'foo'>;  // type is 'foo'
type CheckString = StringLiteral<string>;  // type is never

function makeObject<K>(key: StringLiteral<K>) {
    return { [key]: 'Hello, World!' } as { [P in StringLiteral<K>]: string };
}

const resultWithLiteral = makeObject('hello');  // type is {hello: string;}
let someString = 'prop';
const resultWithString = makeObject(someString); // compiler error.

I don't think unions for K are a problem any more because there is no need to narrow the type for the property key in the makeObject signature. If anything this becomes more flexible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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