Sammy
Sammy

Reputation: 3687

How do I get the key of a value in a Typescript Record

Here's the code:

export type Period = 'dy' | 'wk' | 'mn' | 'qt' | 'yr';

const periods: Record<Period, string> = {
    dy: 'Day',
    wk: 'Week',
    mn: 'Month',
    qt: 'Quarter',
    yr: 'Year'
  };

When I try to do this:

const key = Object.keys(periods).find(key => periods[key] === 'Day');

I get an error of course, since periods[key] cannot guarantee that key is of the correct type. How should I really go about doing this? I thought of an enum but I can't do reverse lookups. All I'm trying to achieve is an input field that displays 'Day' but has a key of dy (etc.) and can set the state to the correct key and not the value when the user picks another value.

Upvotes: 44

Views: 143468

Answers (4)

Tristan Wise
Tristan Wise

Reputation: 21

I may be late to the party but another good way to do this is to use the typescript satisfies operator.

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html

You can do something like

const periods = {
    dy: 'Day',
    wk: 'Week',
    mn: 'Month',
    qt: 'Quarter',
    yr: 'Year'
  } satisfies Record<string, string>

then you can use "keyof typeof periods" to enforce the type of "dy" | "wk" | "mn" | "qt" | "yr" instead of enforcing the type of string

Upvotes: 2

Dave Amphlett
Dave Amphlett

Reputation: 2070

To expand on Daniel Raby's 's answer a little - I have these utility functions for keys and entries which are typed according to the Record<> ...

export function recordKeys<K extends PropertyKey, T>(object: Record<K, T>) {
  return Object.keys(object) as (K)[];
};

export function recordEntries<K extends PropertyKey, T>(object: Record<K, T>) {
  return Object.entries(object) as ([K,T])[];
};

Upvotes: 8

Daniel Raby
Daniel Raby

Reputation: 101

I have found myself needing to solve this many times. I have some utils laying around that help.

This is what I would do.

const _periods = {
  dy: 'Day',
  wk: 'Week',
  mn: 'Month',
  qt: 'Quarter',
  yr: 'Year',
} as const;

export type Period = keyof typeof _periods;
export type PeriodValue = typeof _periods[Period];

export const periods = _periods as Record<Period, PeriodValue>;

function keys<T>(object: T) {
  return Object.keys(object) as (keyof T)[];
};

const key = keys(periods).find((key) => periods[key] === 'Day');

// Compile error, value not possible.
const badValueKey = keys(periods).find((key) => periods[key] === 'Minute');

Upvotes: 10

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249486

Object.keys returns string[] not Array<keyof T> (where T is the type of the value passed in). The reasons for this are outlined here.

Since your object is probably not going to have unknown keys, you can use a type assertion:

export type Period = 'dy' | 'wk' | 'mn' | 'qt' | 'yr';

const periods: Record<Period, string> = {
  dy: 'Day',
  wk: 'Week',
  mn: 'Month',
  qt: 'Quarter',
  yr: 'Year'
};

const key = (Object.keys(periods) as Array<Period>).find(key => periods[key] === 'Day');

Playground Link

Upvotes: 65

Related Questions