32

I want to combine both having a type for a constant, and using it "as const" to get the literal types as the type:

type MyType = {name: string};

const x:MyType = {
    name: 'test' // Autocompleted, typesafe. But Type is {name: string}, not 
                 // what I want, {name: 'test'}
}

const x = { name: 'test' } as const; // Gives correct type, but no type check...

How to do this?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
user2154768
  • 890
  • 3
  • 8
  • 16
  • Note: I kind of obtain what I want by using the type check as I write it, then switch it to "as const" afterwards. Since this silly trick works, I am thinking there must be a proper way... – user2154768 Jun 20 '19 at 21:30
  • 2
    so you want `x` to be a const literal but want to ensure it fits the `MyType` interface? – kmdreko Jun 20 '19 at 21:31
  • 1
    @user2154768 did you find any handy solution? – ZenVentzi May 30 '20 at 20:47

3 Answers3

36

Edit Sep 2022:

With Typescript 4.9 we can now use satisfies operator:

type MyType = { name: string }

const x ={
  name: 'test', // Autocompleted, typesafe
} as const satisfies MyType

x.name // type is 'test' (not string)

And if for some reason, you can't use satisfies, it's fairly easy to replicate the same behavior:

/**
 * Replacement for TS satisfies operator, until it's well supported
 * Usage:
 * const test = satisfy<{ a: string }>()({ a: 'test', b: 'test' })
 * */
export function satisfy<TSatisfied>(): <T extends TSatisfied>(value: T) => T {
  return (value) => value
}

Previous solution:

Here is a way to achieve what you want:

type MyType = { name: string }

// This function does nothing. It just helps with typing
const makeMyType = <T extends MyType>(o: T) => o

const x = makeMyType({
  name: 'test', // Autocompleted, typesafe
} as const)

x.name // type is 'test' (not string)
AnsonH
  • 2,460
  • 2
  • 15
  • 29
Tom Esterez
  • 21,567
  • 8
  • 39
  • 44
1

There is no need to type-check {name: "test"} as const because Typescript uses structural equality meaning aslong as {name: "test"} as const is the same structure of MyType they will be equal and therefore the same.

This behaviour can be observed using these helper types.

interface MyType {
     name: string;
}
const test = {name: "test"} as const;
type IsEqual<T, U> = [T] extends [U] ? true : false;
type AreTheyEqual = IsEqual<typeof test, MyType> // true they are the same.

Anything that takes a MyType can take a typeof Test.

EDIT: If you want to force test to be of type MyType to type-check test there you cannot do this by keeping the string literal because anything asserted to be MyType will lose the literal type and fall back to string this behaviour can be observed here.

type MyType = {name: string};
const x:MyType = {
    name: 'test' as const
}
type Test = typeof x["name"] // string;

Meaning if you want to have both Literal and string types on MyType you will need to instead do something like this (changing MyType). Note using this seems verbose but is arguably less boilerplate than "as const"

interface MyType<NAME extends string = string> {
    name: NAME;
}
const x: MyType = {
    name: 'test' // string;
}
const y: MyType<"test"> = {
    name: "test" // "test"
}

type Test1 = typeof x["name"]// string;
type Test = typeof y["name"] // "test";

^ Both string and literal types allowed.

Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39
  • 3
    They are indeed equal, only problem is as I type, I want to type check that what I typed into const test indeed fullfills MyType. Using as const, there is no such restriction, so I am typing it unsafely. But I can just switch after finish typing though... – user2154768 Jun 20 '19 at 21:51
  • 1
    Thanks, it works but might be hard with more complicated types, I am hoping to build deeply nested literal objects that follows deep interfaces, but maybe the whole thing can be propagated with a single template parameter somehow. I will try some more. – user2154768 Jun 20 '19 at 22:19
  • what is [T] and [U] ? I mean what is the difference between ```type IsEqual = [T] extends [U] ? true : false``` and ```type IsEqual = T extends U ? true : false``` – Archsx Oct 15 '20 at 20:57
1

I don’t think there is any primitive support. But you can do typechecking by defining another const with type assertion.

type MyType = {name: string};

const x = {
    name: 'test' 
} as const

const xTypeCheck = typeof (x as MyType) // Check the type of x as MyType 

Note:I use 'typeof' for avoiding reference of x's value which ensures that x's value can be deleted if there is not any other reference.