I'm working on implementing strict type checking in TypeScript (TS2322). When writing the code like this, the IDE (IntelliJ) correctly validates that these variables are defined as strings:
if (typeof file.key.stringValue !== 'string' ||
typeof file.publishDate.stringValue !== 'string' ||
typeof file.size.stringValue !== 'string' ||
typeof file.url.stringValue !== 'string') {
throw new Error('missing required values')
}
...but it's not as efficient as it could be... I want to write it like this:
for (const key of ['key', 'publishDate', 'size', 'url']) {
if (typeof file[key].stringValue !== 'string') {
throw new Error('missing required values')
}
}
but the IDE continues to throw errors. Any suggestions? Sure, I could write some convenience methods to clean this up, but it doesn't seem like I should need to...
Ultimately, I'm constructing a new object:
const clientFile: ClientFile = {
key: file.key.stringValue,
publishDate: file.publishDate.stringValue,
size: parseInt(file.size.stringValue, 0),
url: file.url.stringValue
}
The error I get from the compiler is: TS2322: Type 'string | undefined' is not assignable to type 'string'.
-- indicating the variables COULD be undefined, hence the frustration.
If I use the first sample of type checking, no error is thrown. If I use the second version, the error persists.
I also tried the suggestion already provided. Same result. Errors persist.
const keys: (keyof typeof file)[] = ['key', 'publishDate', 'size', 'url']
for (const key of keys) {
if (typeof file[key].stringValue !== 'string') {
throw new Error('missing required values')
}
}
UPDATE (8/23/2022)
Based on feedback, I've tried a couple more approaches:
function isString(maybeString: unknown): maybeString is string {
return typeof maybeString === 'string'
}
const keys: (keyof typeof file)[] = ['key', 'publishDate', 'size', 'url']
keys.forEach((key) => {
if (isString(file[key].stringValue)) {
throw new Error(`Missing required value in data: ${key}`)
}
})
const clientFile: ClientFile = {
key: <string>file.key.stringValue,
publishDate: <string>file.publishDate.stringValue,
size: parseInt(<string>file.size.stringValue, 0),
url: <string>file.url.stringValue
}
This uses the type predicate approach, and does NOT require ignoring an eslint rule, but uses a more cumbersome syntax of prefixing with <string>
, whereas this approach doesn't:
const keys: (keyof typeof file)[] = ['key', 'publishDate', 'size', 'url']
keys.forEach((key) => {
if (typeof file[key].stringValue !== 'string') {
throw new Error(`Missing required value in data: ${key}`)
}
})
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const clientFile: ClientFile = {
key: file.key.stringValue!,
publishDate: file.publishDate.stringValue!,
size: parseInt(file.size.stringValue!, 0),
url: file.url.stringValue!
}
/* eslint-enable @typescript-eslint/no-non-null-assertion */
I'm ultimately going with the second option, as it's fewer keystrokes at the cost of an eslint rule, but open to other opinions.