Reputation: 1502
So I'm trying to loop over properties of an object.
interface IProperties {
foo?: {
foo?: string[]
bar?: string[]
}
}
function init(properties: IProperties) {
if (properties.foo) {
for (const [key] of Object.entries(properties.foo)) {
const prop = key as keyof typeof properties.foo
if (properties.foo[prop].length) {
properties.foo[prop] = properties.foo[prop].trim()
}
}
}
}
I'm getting the error message "Property 'length' does not exist on type 'never'". Apparently the "prop" variable is expected to never have a value, but I can't understand why it does that.
EDIT:
Here's the TypeScript Playground
Upvotes: 1
Views: 529
Reputation: 2201
First, we need to note the following points-
The keyof
operator returns never
if the type is "nullable" or can be undefined
type Keys = keyof ({a: string} | undefined)
// type of Keys is never
Next, inside the block of if
, properties.foo
is now not-undefined i.e. it's type is narrow-downed to exclude undefined
. But it loses it's narrowness in the inner scope of the loop (https://github.com/Microsoft/TypeScript/issues/30576).
Ergo, while evaluation the type of keyof typeof properties.foo
, properties.foo
includes undefined
. So, the type assertion resolves to never
.
There are two possible solutions-
NonNullable
in the loopconst prop = key as keyof NonNullable<typeof properties.foo>
if
block.interface IProperties {
foo?: {
foo?: string[]
bar?: string[]
}
}
function init(properties: IProperties) {
if (properties.foo) {
type Keys = keyof typeof properties.foo
for (const [key] of Object.entries(properties.foo)) {
const prop = key as Keys
if (properties.foo[prop]?.length) {
// TODO: fix the below line, properties.foo[prop] is `Array`
// `trim` does not exists on `Array`
// properties.foo[prop] = properties.foo[prop].trim()
}
}
}
}
In my opinion, the best practice is to create type on where other types lie, not in the function definition.
interface IProperties {
foo?: {
foo?: string[]
bar?: string[]
}
}
type IPropertiesFooKeys = keyof NonNullable<IProperties['foo']>
function init(properties: IProperties) {
if (properties.foo) {
for (const [key] of Object.entries(properties.foo)) {
const prop = key as IPropertiesFooKeys
if (properties.foo[prop]?.length) {
// TODO: fix the below line, properties.foo[prop] is `Array`
// `trim` does not exists on `Array`
// properties.foo[prop] = properties.foo[prop].trim()
}
}
}
}
Upvotes: 4