I have a function which is formatting data, and the "values" within this data are well-defined. However, when mapping these values, TS is not picking up on new properties which are invalid. The problem is best explained with code (see the comments in the code):
type TextBoxValue = {
text: string;
}
type TextBox = {
values: TextBoxValue[];
}
type Result = {
result: string | object;
}
const getTheValue = (): Result => ({ result: 'This comes from a child process.' });
const formatTextBox = (textbox: TextBox): TextBox => {
const { result } = getTheValue();
const originalValue = textbox.values[0];
if (typeof result === 'object') {
const arr = Array.isArray(result) ? result : [result];
return {
...textbox,
values: arr.map(res => {
return {
foo: 'bar', // why does this not throw an error
text: res.text || originalValue.text,
};
})
}
}
return {
...textbox,
values: [{
...originalValue,
foo: 'bar', // but this one does?
text: String(result)
}]
}
}
I know I can use satisfies
and other techniques to get the error I want, but I'm curious why TS does not pick up on the invalid property with the code above.
CodePudding user response:
Your map is returning an object of type { foo: string, text: string }
which sufficiently overlaps with TextBoxValue
to keep the compiler happy (but it will treat it as an array of TextBoxValue
s).
That foo
value will not show in any of the type hints and attempting to reach it with (for example) const v = result.values[0].foo
will generate a Property 'foo' does not exist on type 'TextBoxValue'
error.
If you use a type declaration in the map it will show the error
values: arr.map(res => {
// declare type
const textBoxValue: TextBoxValue = {
// Error: Type '{ foo: string; text: any; }' is not assignable to type 'TextBoxValue'.
foo: 'bar',
text: res.text || originalValue.text,
};
return textBoxValue;
})
Similarly, if you use a type assertion
in the final return, you're telling the compiler "trust me ... it's a TextBoxValue" and as long as there is enough overlap to satisfy TextBoxValue
, it will stop complaining.
return {
...textbox,
values: [{
...originalValue,
foo: 'bar', // No more whining
text: String(result)
// assert that I'm a `TextBoxValue`
} as TextBoxValue]
}