Typescript: How to use just one of interfaces with different properties

Created on 6 Dec 2018  路  4Comments  路  Source: microsoft/TypeScript

TypeScript Version: 3.3.0-dev.20181206

Search Terms: interface, union

Code

type WithId = {
  id: number
}

type WithNumber = {
  number: number
}

type User = WithId | WithNumber

const user: User = {
    id: 1,
    number: 1
};

Expected behavior: user cannot have both id and number properties

Actual behavior: user can have both id and number properties. How can I achieve this?

Playground Link: http://www.typescriptlang.org/play/#src=type%20WithId%20%3D%20%7B%0D%0A%20%20%20%20id%3A%20number%0D%0A%7D%0D%0A%0D%0Atype%20WithNumber%20%3D%20%7B%0D%0A%20%20%20%20number%3A%20number%0D%0A%7D%0D%0A%0D%0Atype%20User%20%3D%20WithId%20%7C%20WithNumber%0D%0A%20%20%0D%0Aconst%20user%3A%20User%20%3D%20%7B%0D%0A%20%20%20%20id%3A%201%2C%0D%0A%20%20%20%20number%3A%201%0D%0A%7D%3B

Related Issues: https://github.com/Microsoft/TypeScript/issues/12745, https://github.com/Microsoft/TypeScript/issues/20060, https://github.com/Microsoft/TypeScript/issues/23535

Duplicate

Most helpful comment

Probably worth noting that explicitly listing out all the properties you care about not being in excess with an optional undefined type will make the types work as you'd expect:

type WithId = {
    id: number
    number?: undefined,
}

type WithNumber = {
  id?: undefined,
  number: number
}

type User = WithId | WithNumber

const user: User = {
    id: 1,
    number: 1
};

these are the kinds of types we infer when you're working with fresh literals to accomplish the same thing.

But yes, mostly a duplicate of #20863

All 4 comments

I think this is a duplicate of #20863.

Currently a property is excess in a union type if the property is excess in all branches. In this instance no one property is excess in both branch.

I would be interested to get the opinion from the team as to whether it would be reasonable to have a partial solution that works for 1 or 2 levels deep (but correctly for intersection and unions). I think it would handle most cases people have, but would be tractable to implement and would hopefully retain the performance benefits EPC gives currently.

I will also take this shameless opportunity to plug my experimental work on exact types, which would solve this issue (#28749).

type Exact<T> = {| [K in keyof T]: T[K]; |}

type WithId = {
  id: number
}

type WithNumber = {
  number: number
}

type User = Exact<WithId> | Exact<WithNumber>;

const user: User = {
    id: 1,
    number: 1
};
/*
Type '{ id: number; number: number; }' is not assignable to type 'User'.
  Type '{ id: number; number: number; }' is not assignable to type 'Exact<WithNumber>'.
    Object literal may only specify known properties, and 'id' does not exist in type 'Exact<WithNumber>'. [2322]
*/

Probably worth noting that explicitly listing out all the properties you care about not being in excess with an optional undefined type will make the types work as you'd expect:

type WithId = {
    id: number
    number?: undefined,
}

type WithNumber = {
  id?: undefined,
  number: number
}

type User = WithId | WithNumber

const user: User = {
    id: 1,
    number: 1
};

these are the kinds of types we infer when you're working with fresh literals to accomplish the same thing.

But yes, mostly a duplicate of #20863

Here is a convoluted way to implement @weswigham's suggestion without changing the constituent types. (Disclaimer: I haven't really debugged these).

type WithId = {
    id: number;
}

type WithNumber = {
    number: number;
}

const user: UserExact = { // error
    id: 1,
    number: 1
};

type User = WithId | WithNumber;
type UserExact = Exactify<User>;

type Exactify<T> = Exactify2<T, T>;
type Exactify2<T, U> = T extends unknown ? ExactifyWorker<T, U> : never;
type ExactifyWorker<T, U> = OptionalK<T, MinusKeys<T, U> & string>;
type KeyMap<U> = U extends unknown ? keyof U : never;
type MinusKeys<T, U> = Exclude<KeyMap<U>, keyof T>;
type OptionalK<T, K extends string> = T & { [P in K]?: undefined };

This issue has been marked as a duplicate and has seen no activity in the last day. It has been closed automatic house-keeping purposes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nitzantomer picture nitzantomer  路  135Comments

OliverJAsh picture OliverJAsh  路  242Comments

fdecampredon picture fdecampredon  路  358Comments

xealot picture xealot  路  150Comments

Gaelan picture Gaelan  路  231Comments