Reputation: 3259
I'm trying to describe a union type of a Firestore value:
interface StringValue {
stringValue: string;
}
interface BooleanValue {
booleanValue: boolean;
}
type ValueType = StringValue | BooleanValue;
var value: ValueType = { booleanValue: false, stringValue: "null" }; // [1]
if (value.booleanValue) console.log(value); // [2]
I'm getting an error at [2]:
Property 'booleanValue' does not exist on type 'ValueType'.
Property 'booleanValue' does not exist on type 'StringValue'.(2339)
though I'd expect to error at line [1], where is an incorrect assignment happens. So, why [2] and not [1]?
Upvotes: 0
Views: 61
Reputation: 3259
I've come up with a somewhat sufficient thing:
// makes all props undefined
type Undefined<T> = {
[P in keyof T]: undefined;
};
type Values = {
stringValue: string;
booleanValue: boolean;
}
// picks Key prop from the Dict and marks the rest of them as undefined
type OnlyOne<Dict, Key extends keyof Dict> = Partial<Omit<Undefined<Dict>, Key>> & Pick<Dict, Key>
type StringValue = OnlyOne<Values, 'stringValue'>
type BooleanValue = OnlyOne<Values, 'booleanValue'>
var x: StringValue = { stringValue: 'q' }
var y: BooleanValue = { booleanValue: false }
var shouldFail: BooleanValue = {stringValue: 'a string', booleanValue: false} // fails
type ValueType = StringValue | BooleanValue;
var badValue1: ValueType = { booleanValue: false, stringValue: "null" }; // fails
var badValue2: ValueType = { foo: 'bar' }; // fails
var okValue1: ValueType = { booleanValue: true };
var okValue2: ValueType = { stringValue: 'string' };
if (okValue1.booleanValue) {
var bv: boolean = okValue1.booleanValue; // ok here
var sv: string = okValue1.stringValue; // fails, stringValues is undefined
console.log(okValue1.stringValue);
}
Here is what would work without using Xor
helper type from the related question:
interface StringValue {
kind: 'string',
stringValue: string;
}
interface BooleanValue {
kind: 'boolean',
booleanValue: boolean;
}
type ValueType = StringValue | BooleanValue;
var value: ValueType = { booleanValue: false, stringValue: "null", kind: 'string' }; // [1]
if (value.booleanValue) console.log(value); // [2]
so by extending StringValue
and BooleanValue
with a common kind
field, it errors at both [1] and [2].
Another way is to use XOR
helper type from the answer
Upvotes: 0
Reputation: 2295
It's a better practice to use in
when you try to check if a property exists in an object:
if ('booleanValue' in value)
console.log(value);
This check does not generate an error.
On the other hand, it did not generate an error for the assignment because Typescript checks for minimum properties to exist in an object but if you added more items TS will be Ok with that. Check this part of TS docs for more info.
Upvotes: 1