Reputation: 2364
I was pretty sure Typescript was able to determine an extended class based on a property value, e.g.:
interface Base {
type: string;
child?: Base;
}
interface Ext extends Base {
type: 'test';
sth: string;
}
z({
type: 'a',
child: {
type: 'b',
}
}); // ok
z({
type: 'a',
child: {
type: 'test',
sth: 'val'
}
}); // not ok
function z(input: Base) { }
Above example doesn't work, TS tells me property sth
doesn't exist on interface Base
. What do I need to change so that TS will understand the child to actually be type of Ext
, because of the value 'test'
on the type
property?
Upvotes: 0
Views: 1029
Reputation: 2364
I think I figured it out:
interface Base {
child?: Ext;
}
interface Ext1 extends Base {
type: 'a';
}
interface Ext2 extends Base {
type: 'test';
sth: string;
}
type Ext = Ext1 | Ext2;
z({
type: 'a',
child: {
type: 'test',
sth: 'x'
}
});
function z(input: Ext) { }
This example will fail if sth
is not defined while type
is 'test'
instead of the other way around
Upvotes: 2
Reputation: 51769
This error comes from excess property check, which is done only when variable is initialized with object literal. To avoid it, you need to initialize the value with something which is not an object literal, for example you can add intermediate variable
let o = {
type: 'test',
sth: 'value'
}
let x1: Base = o;
or you can add a type assertion
let x2: Base = {
type: 'test',
sth: 'value'
} as Base;
Another solution is to make Base
and z
generic, parameterized on the type of Child
which should be a subtype of Base
(note that self-referential type constraints are tricky to get right, but it seems to work in this case, the minor issue is with default value for Base
which causes Base
in the constraint to be inferred as Child extends Base<{ type: string; child?: Base<any> | undefined; }
- any
here could be problematic but does not seem to affect anything in the example).
interface Base<Child extends Base = { type: string, child?: Base }> {
type: string;
child?: Child;
}
interface Ext extends Base {
type: 'test';
sth: string;
}
z({
type: 'a',
child: {
type: 'b',
}
}); // ok
z({
type: 'a',
child: {
type: 'test',
sth: 'val'
}
}); // not ok
function z<B extends Base>(input: B) { }
Upvotes: 0
Reputation: 146350
You need to declare it as type Ext
and it should pass
let x: Ext = {
type: 'test',
sth: 'value'
}
Upvotes: 1