2

Having an object like

const translations = {
  say_hello: 'hello {{name}}',
};

I'd like to extract the exact type of the object, like

type Translations = {
  say_hello: 'hello {{name}}',
}

So far I'm playing around something like

type GetExactType<T> = {[K in keyof T]-?: T[K]};

but it doesn't work as expected, because

  GetExactType<typeof translations>;

resolves to

{
   say_hello: string,
}

Any ideas?

2
  • 3
    You can't do it after that initialization of translations; the information you want has already been discarded. You can change the type of the initializer via a const assertion like this but I don't know if that meets your needs. Commented Sep 16, 2022 at 16:12
  • You want GetExactType<typeof translations> to return Translations? Commented Sep 16, 2022 at 16:31

2 Answers 2

1

You could use as const but that results in readonly on everything, which might not be desirable.

Instead, you could have a function to narrow it for you:

type Narrow<T> =
    | (T extends infer U ? U : never)
    | Extract<T, number | string | boolean | bigint | symbol | null | undefined | []>
    | ([T] extends [[]] ? [] : { [K in keyof T]: Narrow<T[K]> });

function narrow<T>(t: Narrow<T>): T {
    return t as T;
}

And then when you use it, it'll infer the types for you:

const translations = narrow({
  say_hello: 'hello {{name}}',
});

type T = typeof translations;
//   ^? { say_hello: 'hello {{name}}'; }

Note that it is impossible to retrieve the original type after you define it like this:

const translations = {
  say_hello: 'hello {{name}}',
};

so some sort of direct operation on the value before it is assigned is required.

Playground

Sign up to request clarification or add additional context in comments.

1 Comment

Fascinating. I suspect, given this is what appears to be translation strings, the as const would be sufficient. But really cool to have that Narrow type in the toolkit...
1

You have to tell TypeScript that the say_hello property's type is 'hello {{name}}', not string. There are at least a couple of ways to do that:

  1. Via as const, saying that the object won't change:

    const translations = {
        say_hello: 'hello {{name}}',
    } as const;
    //^^^^^^^^
    type Translations = typeof translations;
    
  2. Explicitly:

    const translations = {
        say_hello: 'hello {{name}}' as 'hello {{name}}',
    // −−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^
    };
    type Translations = typeof translations;
    

    (but...ewww...)

    or

    const translations: { say_hello: 'hello {{name}}' } = {
        say_hello: 'hello {{name}}',
    };
    type Translations = typeof translations;
    // (But if you were going to do that, you'd just want to write the type
    // alias manually and then apply it to the `translations` constant, rather
    // than doing it this way around.)
    

I use the as const for things like translation strings all the time.

Playground link

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.