typescript interface: optional but not undefined

Viewed 3471

How can I describe type like

key is optional, but if it existed, it cannot be undefined.

At the first time, I'm using Partial to make all properties of T optional.

interface A {
  a: number;
}

var data: A = {
  a: 1,
}

var update1: Partial<A> = {

}

var update2: Partial<A> = {
  a: undefined
}

var result1: A = {
  ...data,
  ...update1,
} // { a: 1 }

var result2: A = {
  ...data,
  ...update2,
} // { a: undefined }

The problem here is that result2 is not implementing interface A in runtime, but typescript never complains about it. Is it bug or feature? I guess typescript not working well with spread operator...

The goal is distinguish these two variable using typescript!


var data: T = {
  a: 1,
  b: 2,
}

var optional1: MyPartial<T> = { // Totally OK

}

var optional2: MyPartial<T> = { // OK
  a: 3,
}

var optional3: MyPartial<T> = { // OK
  b: 4,
}

var optional4: MyPartial<T> = { // OK
  a: 3,
  b: 4,
}

var undef1: MyPartial<T> = { // typescript must throw error
  a: undefined,
}

var undef2: MyPartial<T> = { // typescript must throw error
  a: 3,
  b: undefined
}

...

Check this out TypeScript playground example.

3 Answers

As @dtanabe mentioned, there isn't really a good solution here until TypeScript support (if they decide to). And main problem from doing it in user land is that in a var def (i.e. const x: Foo = ...) we do not have access to the type of the value. If you are OK changing your code from using spread to using a function you can do something like this:

interface A {
  a: number;
}

type EnsurePartial<TTarget, TUpdate extends keyof TTarget> =
  undefined extends TTarget[TUpdate]
  ? never
  : { [Key in TUpdate]: TTarget[Key] };

declare function update<T>(propsToUpdate: EnsurePartial<A, T>): void

update({}) // OK
update({ a: 42 }) // OK
update({ a: undefined }) // error
update({ c: undefined }) // error

There is an error in Dmitriy answer, here is the fixed version

interface A {
  a: number;
  b: boolean
}

type PartialWithoutUndefined<M,T> = M extends T
    ?  undefined extends M[keyof M]
            ? never
            : M
    : never


declare function update<M>(propsToUpdate: PartialWithoutUndefined<M,Partial<A>>): void

update({}) // OK
update({ a: 42 }) // OK
update({ a: undefined }) // error
update({ c: undefined }) // error

playground

weakness: it is impossible to type anything as undefined even if that is your intention

Related