Reputation: 2025
I have a number of Item
objects and a method that processes them. The issue is that some Items could potentially have certain properties that others don't.
type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';
export default interface Item {
ref?: string;
position: Position;
type: ItemType;
children?: Item[];
}
function process(item: Item) {...}
Now, let's say I want to create a separate interface ApiGatewayMethodItem
(that extends Item
) that has an additional string property called 'method' representing whether it's a GET or POST or something else. Is there a way I can type it in such a way that as soon as I type process({type: 'ApiGatewayMethod'})
tsc will start complaining about the missing property method
? I understand that TS has a pretty good support for the "conditional types", but I haven't used them before and I'm having a hard time wrapping my head around them...
So let's say I have the interface
interface ApiGatewayMethodItem extends Omit<Item, 'type'> {
type: 'ApiGatewayMethod';
method: string;
}
now when I call the process
function, I need the compiler to complain that the method
property is missing when I don't specify it, but specify the type ApiGatewayMethod
Upvotes: 1
Views: 260
Reputation: 595
You could define your item type with only the 2 ItemTypes that doesn't have the property 'method'. Then add the property 'method' only to the type 'ApiGatewayMethodItem'
type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint';
export default interface DefaultItem {
ref?: string;
position: Position;
type: ItemType;
children?: DefaultItem[];
}
interface ApiGatewayMethodItem extends Omit<DefaultItem, 'type'> {
type: 'ApiGatewayMethod';
method: string;
}
type Item = DefaultItem | ApiGatewayMethodItem;
function process(item: Item) {
}
process ({
type: 'ApiGatewayMethod',
position: null
})
Upvotes: 2
Reputation: 2828
You could solve this by using discrimated unions. This way the compiler can figure out which members are available from the type
field.
type ItemTypeWithoutMethod = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';
type ItemTypeWithMethod = "ApiGatewayMethodItem";
interface ItemBase {
ref?: string;
position: Position;
children?: Item[];
}
interface ItemWithoutMethod extends ItemBase {
type: ItemTypeWithoutMethod
}
interface ItemWithMethod extends ItemBase {
type: ItemTypeWithMethod;
method: string;
}
export type Item = ItemWithMethod | ItemWithoutMethod;
function doSomethingWithItem(item: Item) {
if (item.type == "ApiGatewayMethodItem") {
console.log(item.method);
}
}
Upvotes: 0
Reputation: 6476
You can make Item
a union type, and define an interface which holds the properties which are common to all items:
export default interface CommonItemProperties {
ref?: string;
position: Position;
children?: Item[];
}
interface ApiGatewayMethodItem extends CommonItemProperties {
type: 'ApiGatewayMethod';
method: 'GET' | 'POST';
}
interface ApiGatewayEndpointItem extends CommonItemProperties {
type: 'ApiGatewayEndpoint';
path: string;
}
type Item = ApiGatewayEndpointItem | ApiGatewayMethodItem;
function process(item: Item) {
switch (item.type) {
case ('ApiGatewayMethod'):
return item.method; // this is not a type error - typescript 'knows' the item is a ApiGatewayMethodItem
case ('ApiGatewayEndpoint'):
return item.path; // this is not a type error - typescript 'knows' the item is a ApiGatewayEndpointItem
}
}
The switch
statement is just an example of how typescript can determine the type of item you've passed in based on a check against type
.
Upvotes: 1