Is it possible to discriminate unstructured values of an object with union type in Typescript?

Asked

Viewed 47 times

6

I realized that by performing the breakdown in the signature of the function (funcao({ a, b }: Objeto)), I end up losing a certain consistency of the types. In the specific case where I realized this, always only one of the properties is defined (a or b), but when performing the de-structuring, Typescript "forgets" this.

For example:

type CatsOrDogs =
  | { cats: string[]; dogs?: undefined }
  | { cats?: undefined; dogs: string[] };

// Funciona
function test(obj: CatsOrDogs) {
  if (obj.cats !== undefined) {
    obj.cats.push('');
  } else {
    obj.dogs.push('');
  }
}

// Não funciona
function testDestructuring({ cats, dogs }: CatsOrDogs) {
  if (cats !== undefined) {
    cats.push('');
  } else {
    dogs.push(''); // Object is possibly 'undefined'.(2532)
  }
}

See on Playground.

  • How can I raise this guy CatsOrDogs without losing consistency in structuring?

  • Or, there is another way to have the desired behavior without giving up the use of destructuring?

2 answers

4


In short, it is not possible to do the de-structuring before the branching and also carry out discrimination of union types.

Notice that when you do the de-structuring before the branching (in that case by if), the compiler cannot define the type of the unstructured value by the location where it is used. Because of this, the type inferred for this value will be as generic as possible. In the case of unions, the type shall be the union of all variants corresponding to the property in question.

So:

type CatsOrDogs =
  | { cats: string[]; dogs?: undefined }
  | { cats?: undefined; dogs: string[] };

const catsOrDogs: CatsOrDogs = {} as any; // Ignore isso, só para demonstrar o valor.

const { cats } = catsOrDogs;
// Tipo de `cats` será `string[] | undefined`.

In this example, it is not feasible to expect the compiler to associate with the variable type cats the conditions under which it may be string[] or undefined. Therefore, the inferred type will be string[] | undefined.

You can, however, perform the de-structuring once the branching has already been realized:

type CatsOrDogs =
  | { cats: string[]; dogs?: undefined }
  | { cats?: undefined; dogs: string[] };

const catsOrDogs: CatsOrDogs = {} as any; // Ignore isso, só para demonstrar o valor.

if (catsOrDogs.cats) {
  const { cats } = catsOrDogs;
  // Dentro deste branch, o tipo de `cats` será `string[]`.
}

It is important to keep in mind that, in unions, the information necessary for the compiler to discriminate is in the whole object (which has the Union type). Once you unstructure an object before the compiler has made the discrimination, unstructured values will not have any skill to accomplish it.

-2

Try this:

function testDestructuring({ cats, dogs }: CatsOrDogs) {
  cats?.push('')
  dogs?.push('');
}

The guy inference works, you can see that by hovering your mouse over cats or dogs within the function. There is described what the type of variable will be. In your case Cats can be both an array and can be Undefined. The same goes for dogs.

  • Take the example test. I’m sure either cats or dogs is an array, and the other is undefined. The problem with dismantling is that I need to use the optional chaining ?. in the else of my example testDestructuring. What I want is just not to use the ?., since I know it won’t be Undefined.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.