Reputation: 77
I'm trying to get intellisense working with an interface with a generic type, I'd like intellisense to and typescript checking to work for the properties of the children but I can't figure out how to write my interface.
This is a very simplified example of what I'm trying to do
interface Foo1 {
prop1: string;
prop2: boolean;
}
interface Foo2 {
prop3: string;
prop4: boolean;
}
interface Bar1 {
thing1: Foo1;
thing2: Foo2;
}
interface Bar2 {
thing3: Foo1;
thing4: Foo2;
}
Given a structure like this (in my project there are alot more Foo's Bar's and they all have far more properties) I want to be able to use the chosen key to produce something like this
interface TestItem<T> {
field: keyof T; //This will be a key of a Bar object
select: (keyof T[keyof T])[]; //This is trying to state that elements in this array must be a property of Bar[field], e.g if field is "thing1" the only valid elements would be "prop1" or "prop2"
}
interface TestObject<T> {
itemArray: TestItem<T>[];
}
let test: TestObject<Bar1> = {
itemArray: [
{
field: "thing1",
select: ["prop1"] //error: string is not assignable to type never
}, {
field: "thing2",
select: ["prop3"] //error: string is not assignable to type never
}
]
}
I didn't expect this to work but I've tried so many things with no better results. I know I could just add more GenericType arguments and specify what is getting passed in to each TestItem but I'm hoping someone knows a way that specifying the field will be enough for Typescript to work out the valid values for select in TestItem.
Upvotes: 2
Views: 4807
Reputation: 51609
It can't be done with a single-step object assignment - there is no way to restrict type of an object property based on values of other properties in object initialization.
But with slightly different syntax it's possible:
class TestObject<T> {
itemArray: { field: keyof T, select: string[] }[];
// no constraint could be specified for select here
// because there is not enough information about T here
// but we can define a function that will apply constraint
// when called from a subclass
testItem<K extends keyof T>(item: { field: K, select: (keyof T[K])[] }) {
return item
}
}
class TestBar1 extends TestObject<Bar1> {
itemArray = [
this.testItem({ field: "thing1", select: ["prop1"] }), // ok
this.testItem({ field: "thing2", select: ["prop3"] }), // ok
this.testItem({ field: "thing1", select: ["prop3"] }) // error:
// Argument of type '{ field: "thing1"; select: "prop3"[]; }'is not assignable
// to parameter of type '{ field: "thing1"; select: ("prop1" | "prop2")[]; }'.
// Types of property 'select' are incompatible.
// Type '"prop3"[]' is not assignable to type '("prop1" | "prop2")[]'.
// Type '"prop3"' is not assignable to type '"prop1" | "prop2"'.
]
}
let test = new TestBar1().itemArray;
when the erroneous item is removed, the type inferred for test
is as expected:
({ field: "thing1"; select: ("prop1" | "prop2")[]; }
| { field: "thing2"; select: ("prop3" | "prop4")[]; })[]
Upvotes: 2
Reputation: 11020
Not quite sure if I understand your question correctly, but this works:
interface Foo1 {
prop1: string;
prop2: boolean;
}
interface Foo2 {
prop3: string;
prop4: boolean;
}
interface Bar1 {
thing1: Foo1;
thing2: Foo2;
}
interface Bar2 {
thing3: Foo1;
thing4: Foo2;
}
interface TestItem<T, K> {
field: keyof T;
select: (keyof K)[]; //This keyof T being the same as field
}
interface TestObject<T, K> {
itemArray: TestItem<T, K>[];
}
let test: TestObject<Bar1, Foo1> = {
itemArray: [
{
field: "thing1",
select: ["prop1"]
}, {
field: "thing2",
select: ["prop2"]
}
]
}
Your example couldn't work because neither prop1
nor prop2
are owned by Bar1
, so you have to extend your generic by another one (using K
here)
Upvotes: 0