1

I would like to have a type that ensures an object has a type of A or B or A and B. However one of the cases that I think should fail is not. I'm sure it's something stupid I just can't see it yet.

interface ValueSelector
{
    type: "id" | "value_string"
    value: string
}

interface TemporalSelector
{
    id: number
}

type Selector = (ValueSelector & TemporalSelector) | ValueSelector | TemporalSelector

// Should error
const e0: Selector = {}
const e1: Selector = { id: 0, value: "" }  // <-- does not error
const e2: Selector = { type: "id" }
const e3: Selector = { type: "value_string" }
const e4: Selector = { value: "" }
const e5: Selector = { value: "" }

// Should pass
const a1: Selector = { id: 0 }
const a2: Selector = { type: "id", value: "" }
const a3: Selector = { type: "value_string", value: "" }
const a4: Selector = { id: 0, type: "id", value: "" }
const a5: Selector = { id: 0, type: "value_string", value: "" }

1 Answer 1

2

e1 does not trigger an error because { id: 0, value: "" } is already assignable to TemporalSelector since it expects only id property.

In order to make it work you can use the StrictUnion helper:

interface ValueSelector {
    type: "id" | "value_string"
    value: string
}

interface TemporalSelector {
    id: number
}

type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> = 
    T extends any 
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>


type Selector = (ValueSelector & TemporalSelector) | StrictUnion<ValueSelector | TemporalSelector>

// Should error
const e0: Selector = {}
const e1: Selector = { id: 0, value: "" }  // error
const e2: Selector = { type: "id" }
const e3: Selector = { type: "value_string" }
const e4: Selector = { value: "" }
const e5: Selector = { value: "" }

// Should pass
const a1: Selector = { id: 0 }
const a2: Selector = { type: "id", value: "" }
const a3: Selector = { type: "value_string", value: "" }
const a4: Selector = { id: 0, type: "id", value: "" }
const a5: Selector = { id: 0, type: "value_string", value: "" }

Playground

Sign up to request clarification or add additional context in comments.

2 Comments

This is great. Thank you. I usually forget this Liskov Substitution Principle/rule. The StrictUnion is exactly what I need. The tag property will not work however as they allow A or B but not A or B or A and B.
Thank you. Also I did not think about Liskov SP in this context. Nice

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.