Object.keys with typescript
A common issue when using Object.keys
with typescript is that the type of the returned array is string[]
instead of the correct type of the object's keys. This makes it impossible to use the returned array to access the object's properties.
Here it is a example of this issue:
// Declare an object
const unConfirmedTargets = {
email: false,
phoneNumber: true,
telegram: false,
discord: true,
};
// Get the object's keys and manipulate them.
Object.keys(unConfirmedTargets)
.map((key) => {
if (unConfirmedTargets[key]) {
return key;
}
})
.filter((item): item is FormField => !!item);
Then we will get the following error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ email: boolean; phoneNumber: boolean; telegram: boolean; discord: boolean; }'.
No index signature with a parameter of type 'string' was found on type '{ email: boolean; phoneNumber: boolean; telegram: boolean; discord: boolean; }'.ts(7053)
The reason is that the type of Object.keys(unConfirmedTargets)
is string[]
. Typescript does not know that the keys explicitly.
To fix this, we can create a helper:
/**
* @description Returns an array of the object's keys with the correct type.
* @example
* const destinations: Record<FormField, string> = { email: 'email', phoneNumber: 'phoneNumber' };
* const keys = objectKeys(destinations); // type of Keys is FormField[] instead of string[] (which is the default type of Object.keys)
*/
export const objectKeys = <T extends Record<keyof T, unknown>>(
object: T
): (keyof T)[] => {
return Object?.keys(object) as (keyof T)[];
};
Advanced usage
Say we have a parsed markdown html that we want to override the style of some html tags. We can do it like this:
In this case, we need to use Object.keys
to get the keys of the object markdownHtmlAttrs
and then use forEach
to iterate over the keys. This is where the objectKeys
helper comes in handy.
Just like this:
const markdownHtmlAttrs = {
a: "class='cursor-pointer text-wosmongton-300 transition-all duration-[0.2s] hover:text-osmoverse-200' target='_blank' ",
li: "class='list-disc list-inside'",
};
const overrideMarkdownHtmlStyle = (htmlMessage: string) => {
let updatedHtmlMessage = htmlMessage;
objectKeys(markdownHtmlAttrs).forEach((tag) => {
updatedHtmlMessage = updatedHtmlMessage.replace(
new RegExp(`<${tag}`, "g"),
`<${tag} ${markdownHtmlAttrs[tag]}`
);
});
return updatedHtmlMessage;
};
Otherwise, we will need to cast the type of Object.keys
to (keyof typeof markdownHtmlAttrs)[]
like this, which is not as clean as the objectKeys
helper:
const markdownHtmlAttrs = {
a: "class='cursor-pointer text-wosmongton-300 transition-all duration-[0.2s] hover:text-osmoverse-200' target='_blank' ",
li: "class='list-disc list-inside'",
};
const overrideMarkdownHtmlStyle = (htmlMessage: string) => {
let updatedHtmlMessage = htmlMessage;
Object.keys(markdownHtmlAttrs).forEach((tag) => {
updatedHtmlMessage = updatedHtmlMessage.replace(
new RegExp(`<${tag}`, "g"),
`<${tag} ${markdownHtmlAttrs[tag as keyof typeof markdownHtmlAttrs]}`
);
});
return updatedHtmlMessage;
};