I have an object of type Record<string, unknown>
and I am using .reduce()
to iterate over all the properties.
Now I want to perform an action on each of the property values that are strings.
But I can't figure out how to narrow the property value from unknown
to string
.
My code looks something like this:
const obj: Record<string, unknown> = {
foo: 'bar',
}
Object.keys(obj).reduce((result, key) => {
if (typeof obj[key] === 'string') {
const stringValue = obj[key]; // 'stringValue' should be 'string' type, but is currently 'unknown'
// Do something here with a string
}
return {
...result,
};
}, {});
Here is the TypeScript playground link.
CodePudding user response:
Store the value in a variable instead of accessing it on the object. A variable's type can be narrowed, but not an object's properties (source)
Object.keys(obj).reduce((result, key) => {
const val = obj[key];
if (typeof val === 'string') {
// Do something here with val as a string
}
return {
...result,
};
}, {});
CodePudding user response:
You can first assign the value at the property to a local variable, then narrow that variable:
const obj: Record<string, unknown> = {foo: 'bar'};
const onlyStrings: Record<string, string> = {};
for (const key in obj) {
const value = obj[key];
//^? const value: unknown
onlyStrings[key] = value; /*
~~~~~~~~~~~~~~~~
Type 'unknown' is not assignable to type 'string'.(2322) */
if (typeof value === 'string') {
onlyStrings[key] = value; // ok
//^? const value: string
}
}
Or, you can use a function which returns a type predicate (such a function is called a type guard) to narrow the object type directly:
const obj: Record<string, unknown> = {foo: 'bar'};
const onlyStrings: Record<string, string> = {};
function objHasStringValueAtKey <K extends string, T extends Record<K, unknown>>(
obj: T,
key: K,
): obj is T & Record<K, string> {
return typeof obj[key] === 'string';
}
for (const key in obj) {
if (objHasStringValueAtKey(obj, key)) {
onlyStrings[key] = obj[key]; // ok
}
}
In your example, the narrowing expression is simple, so I'd probably use the former approach, but with more complicated data types and structures, a type predicate can very neatly encapsulate the runtime validation that needs to be performed in order to maintain type safety and help reduce syntax complexity in the main application logic code, making it easier to reason about.