I am trying to narrow a variable of type unknown
to Record<PropertyKey, unknown>
without using type assertions, but I am getting the following error;
Type 'object' is not assignable to type 'Record<PropertyKey, unknown>'. Index signature for type 'string' is missing in type '{}'.ts(2322)
How can I improve the following code without using a type assertion or custom type guard (like variable is Record<PropertyKey, unknown>
) ? The only other question I found didn't appear to have a concrete answer.
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
if (
typeof variable !== "object" ||
Array.isArray(variable) ||
variable === null
) {
throw new Error("Not Record");
}
return variable
};
I believe I need to tell the compiler that there is at least one property of either string
, number
or symbol
on the narrowed object, but I'm not sure how.
And for posterity, if I need to also support the possibility of parsing an empty object, can I use the same signature, or would I need to do something like Record<PropertyKey, unknown> | {}
? I ask because unknown[]
neatly covers both an empty array and a populated array.
TypeScript currently does not narrow from the unknown
type to Record<PropertyKey, unknown>
. The closest you can get is narrowing to the object
type, but since object
is apparently not assignable to Record<PropertyKey, unknown>
, this doesn't help you much. There is a longstanding open feature request at microsoft/TypeScript#38801 to allow the narrowing you're looking for, but it hasn't been implemented and apparently it might be a breaking change if it were, so for now it's not part of the language.
You can always just assert than an object
is a Record<PropertyKey, unknown>
,
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
if (
typeof variable !== "object" ||
Array.isArray(variable) ||
variable === null
) {
throw new Error("Not Record");
}
return variable as Record<PropertyKey, unknown>
};
or, if you think you'll need such narrowing to happen in lots of places, you could refactor it to a custom type guard function which always returns true
(since we presume that checking if a value is a non-null object is sufficient):
function objectIsRecordUnknown(o: object): o is Record<PropertyKey, unknown> {
return true; // this is effectively always true
}
const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
if (
typeof variable !== "object" ||
Array.isArray(variable) ||
variable === null ||
!objectIsRecordUnknown(variable) // no-op
) {
throw new Error("Not Record");
}
return variable
};
Then objectIsRecordUnknown(variable)
is always true, but now it allows TS to narrow from object
as desired. If you're only doing the narrowing once, though, this is just a more roundabout way of using a type assertion (it's no safer).