Eric Chen
Eric Chen

Reputation: 95

Typescript: How to extract the value types of a const object and use them as keys in a new type?

I want to take a constant object, say {key1: 'value1', key2: value2'} as const, and convert its values into keys and use it in a new object with type {value1: number; value2: number}. I'd like to do this in a function, and I want the function to be typed correctly.

Below is the code that I'm trying to get to work. The error is happening when I try to cast a variable of type Partial<Blah> to Blah. Could someone explain why it's happening, and if there's a way to do what I'm trying to do? Thanks!

type BaseObject = Record<string, string>;


type ValuesAsKeys<T extends BaseObject> = {
    [K in keyof T as T[K]]: number;
};

function useValuesAsKeys<T extends BaseObject>(arg: T): ValuesAsKeys<T> {
  const x: Partial<ValuesAsKeys<T>> = {};
  for (const value of Object.values(arg) as Array<T[keyof T]>) {
    x[value] = 1;
  }

  // This line doesn't work
  return x as ValuesAsKeys<T>;
}

// Example use case
const obj = {key1: 'value1', key2: 'value2'} as const;
const result = useValuesAsKeys(obj); // Type of result is {value1: number; value2: number}


// Everything below here works
// Only including this because I'm contrasting this code with the code above.

type KeysAsKeys<T extends BaseObject> = {
    [K in keyof T]: number;
};

function useKeysAsKeys<T extends BaseObject>(arg: T): KeysAsKeys<T> {
    const x: Partial<KeysAsKeys<T>> = {};
  for (const value of Object.values(arg) as Array<T[keyof T]>) {
    x[value] = 1;
  }
  return x as KeysAsKeys<T>;
}

Upvotes: 5

Views: 9850

Answers (3)

user23611368
user23611368

Reputation: 41

There is a simpler way to accomplish that.

const obj = { key1: 'value1', key2: 'value2' } as const;

type ValuesAsKeys = typeof obj[keyof typeof obj];

const myUseOfObject: { [key in ValuesAsKeys]: number } = {
    value1: 1,
    value2: 2
};

Upvotes: 4

Eric Chen
Eric Chen

Reputation: 95

I'm still not sure why casting Partial<Blah> to Blah doesn't work in this scenario, but this code works and is type-safe enough for me:

function useValuesAsKeys<T extends BaseObject>(arg: T): ValuesAsKeys<T> {
  const x = {} as ValuesAsKeys<T>;
  for (const value of Object.values(arg) as Array<T[keyof T]>) {
    x[value] = 1;
  }
  return x;
}

Upvotes: 0

Explicit return type with reduce almost always does not work, because initial values changes every iteration.

In order to make it work, it is better to overload your function:

type BaseObject = Record<string, string>;


type ValuesAsKeys<T extends BaseObject> = {
  [K in keyof T as T[K]]: number;
};

type Values<T> = T[keyof T]

function useValuesAsKeys<T extends BaseObject>(arg: T): ValuesAsKeys<T>
function useValuesAsKeys<T extends BaseObject>(arg: T) {
  return (Object.values(arg) as Array<Values<T>>).reduce((acc, elem) => ({
    ...acc,
    [elem]: 1
  }), {})
}

// Example use case
const obj = { key1: 'value1', key2: 'value2' } as const;

const result = useValuesAsKeys(obj); // Type of result is {value1: number; value2: number}
result.value1 //ok

Playground

docs

Upvotes: 1

Related Questions