I'm using typescript on a project and in some parts of it I have to use union types. But I'm getting some weird error messages that I don't know how to deal with. Consider the type below:
type body = {
[_: string]:
| 'boolean'
| 'number'
| 'string'
| {
type: 'boolean' | 'number' | 'string'
optional?: boolean
}
| {
type: 'array'
items: 'boolean' | 'string' | 'number'
optional?: boolean
[_: string]: any
}
}
I'm using [_: string]:
because I should be able to use any arbitrary key. The value can either be a string indicating a type or can be a object that provides more details.
Now consider the following function:
function show(data: body) {
console.log(data)
}
When I call the above function with the object below:
const data = {
username: { type: 'string', optional: false },
address: { type: 'string' },
city: 'string'
}
Typescript gives the error below:
Argument of type '{ username: { type: string; optional: boolean; }; address: { type: string; }; city: string; }' is not assignable to parameter of type 'body'.
Property 'username' is incompatible with index signature.
Type '{ type: string; optional: boolean; }' is not assignable to type '"string" | "number" | "boolean" | { type: "string" | "number" | "boolean"; optional?: boolean | undefined; } | { [_: string]: any; type: "array"; items: "string" | "number" | "boolean"; optional?: boolean | undefined; }'.
Property 'items' is missing in type '{ type: string; optional: boolean; }' but required in type '{ [_: string]: any; type: "array"; items: "string" | "number" | "boolean"; optional?: boolean | undefined; }'.
How can I solve this? thanks
TypeScript Union Type Narrowing Since a variable of a union type can assume one of several different types, you can help TypeScript infer the correct variable type using type narrowing. To narrow a variable to a specific type, implement a type guard.
TypeScript allows us to use more than one data type for a variable or a function parameter. This is called union type. Consider the following example of union type. In the above example, variable code is of union type, denoted using (string | number) .
TypeScript 1.4 gives programs the ability to combine one or two types. Union types are a powerful way to express a value that can be one of the several types. Two or more data types are combined using the pipe symbol (|) to denote a Union Type.
In TypeScript, a union type variable is a variable which can store multiple type of values (i.e. number, string etc). A union type allows us to define a variable with multiple types. The union type variables are defined using the pipe ( '|' ) symbol between the types.
You can explictly declare what type your property data
is
const data: body = {
username: { type: 'string', optional: false },
address: { type: 'string' },
city: 'string'
}
or cast it
const data = {
username: { type: 'string', optional: false },
address: { type: 'string' },
city: 'string'
} as body;
The issue here is with an overly broad inference on the type of the data
variable. Take a look at the reproduction here.
If you hover your data
variable, you'll note that it was given the inferred type:
{
username: {
type: string;
optional: boolean;
};
address: {
type: string;
};
city: string;
}
Breaking down your error message, we see the first line:
Property 'username' is incompatible with index signature.
Indicates that username
is the issue here. As you can see from the inferred type, that property has the type {type: string, optional: boolean}
.
You might think that this would match to the
{ type: 'boolean' | 'number' | 'string', optional?: boolean }
part of your union, however the critical difference is that this part of your union requires the type
property to have one of the literal string types 'boolean' | 'number' | 'string'
, and does not permit any string (represented by the type string
(without quotes)).
This is why it then tries to match to the final piece of your union, which would allow the type: string
key/value pair due to the index signature. But this last part of the union also requires an Items
property which is what is leading to the specific error you see.
So how do you fix it? Well you need to give typescript a hint that the username.type
property of your data
object is not just any string. Here are some various ways:
body
type, as in Andreas' excellent answer.type: 'string' as 'string'
. You'd have to do this for all three properties so this is not very ideal in this particular case.const data = { ... } as const
show({
username: { type: 'string', optional: false },
address: { type: 'string' },
city: 'string'
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With