Reputation: 1148
I have an object that can either look like this:
{ageTop:42}
or like this:
{locations: [{name:Myrtle Beach}]}
These objects are passed as a parameter to a function. (I'm trying to make sure the function only gets the types that it can use, there may be one of these, there may be two of these there may be none of these)
the solution I have come up with is as follows:
I have defined the interface like so:
interface locationsType {
locations: Array<{name: string}>
}
interface ageTopType {
ageTop: number
}
type keyType = 'ageTop' | 'locations'
I am enforcing the fact that if locations is selected we use the locationsType interface through convention in my code, I believe this will lead to errors later on when someone (or myself) breaks the convention. How can I enforce this through typing? Basically I'd like to create some simple logic as:
interface traitType {
key: keyType
value: this.key === 'locations' ? locationsType : ageTopType;
}
class Developer {
locations: []
ageTop;
constructor(ageTop,locations) {
this.ageTop = ageTop
locations.forEach(_val => this.locations.push(_val))
}
loadTrait(trait:TraitType) {
if(trait.key === location) {
this.trait.value.forEach(_val => this.locations.push(_val)
return
}
this[trait.key] = trait.value;
}
}
The above has been tried and does not work lol. Any insights?
Upvotes: 3
Views: 5008
Reputation: 13539
you can try a union of types
interface locationsType {
locations: Array<{name: string}>;
}
interface ageTopType {
ageTop: number;
}
type keyType = 'ageTop' | 'locations';
enum traitTypes {
location = 'locations',
ageTop = 'ageTop',
}
type traitType = {
key: traitTypes.location;
value: Array<locationsType>;
} | {
key: traitTypes.ageTop;
value: ageTopType;
};
class Developer {
public locations: Array<locationsType> = [];
public ageTop: ageTopType;
constructor(ageTop: ageTopType, locations: Array<locationsType>) {
this.ageTop = ageTop;
this.locations.push(...locations);
}
public loadTrait(trait: traitType): void {
if (trait.key === traitTypes.location) {
this[trait.key].push(...trait.value);
} else if (trait.key === traitTypes.ageTop) {
this[trait.key] = trait.value;
}
}
}
Upvotes: 1
Reputation: 328097
The conventional way to do this is via a discriminated union, where the members of the union have a common disciminant property that you test to narrow a value of the union type to one of the members. So given
interface LocationsType {
locations: Array<{ name: string }>
}
interface AgeTopType {
ageTop: number
}
We can define TraitType
to be a discriminated union like this:
type TraitType =
{ key: 'ageTop', val: AgeTopType } |
{ key: 'locations', val: LocationsType };
And then you can see how the compiler automatically uses control flow analysis to refine the type of a TraitType
when the discriminant is tested:
function foo(t: TraitType) {
if (t.key === "ageTop") {
t.val.ageTop.toFixed();
} else {
t.val.locations.map(x => x.name.toUpperCase());
}
}
Okay, hope that helps; good luck!
Upvotes: 5