Reputation: 3687
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
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
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
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
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');
Upvotes: 65