0
interface Type1 {
  attr1: string;
  attr2: string;
}
interface Type2 {
  attr1: string;
  attr2: string;
  attr3: string; // extra attribute
}

function fn(config: Type1 | Type2): void {
  // Property 'attr3' does not exist on type 'Type1 | Type2'.ts(2339)
  const { attr1, attr2, attr3 } = config;
  console.log(attr1);
  console.log(attr2);
  console.log(attr3);
}

Error code show before. And I know there is a solution that add optional attribute in attr3. But as far as I'm concerned this solution is not good. Because as matter of fact there only exist 2 situations either Type1 or Type2. In a word, optional way is not readability. How can I fix it in advanced way?

2
  • What is the expected outcome? Because your types say that you do not have attr3, so what value do you expect when trying to get a non-existing attribute? Commented May 19, 2021 at 7:08
  • Just remove Type1 and make attr3 optional or catchts.com/unions#safe_union Commented May 19, 2021 at 8:52

2 Answers 2

1

You could just simply check if attr3 exists in the object

function fn(config: Type1 | Type2): void {
  if ('attr3' in config) {
    const {
      attr1,
      attr2,
      attr3
    } = config;
  } else {
    const {
      attr1,
      attr2
    } = config;
  }
}

Or you could use a custom type guard

function IsType2(config: Type1 | Type2): config is Type2 {
  return (config as Type2).attr3 !== undefined;
}

function fn(config: Type1 | Type2): void {
  if (IsType2(config)) {
    const {
      attr1,
      attr2,
      attr3
    } = config;
  } else {
    const {
      attr1,
      attr2
    } = config;
  }
}

Or if we really wanted to destructure only once, we could create a join type, though we would be coercing the type rather than inferring it.

function fn(config: Type1 | Type2): void {
  const {
    attr1,
    attr2,
    attr3
  } = config as Type1 & Type2;
}

As far as I know, there's no way to directly destructure a union type

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

7 Comments

And how do you use those variables outside the if? Or do you just copy/paste the code in each branch?
You could copy/paste the code in each branch, or you could extract the processing logic into isolated functions to reduce WET code. That seems slightly beyond the scope of the question though.
The question is how to handle it better. I'm not sure an if fits that criteria.
OP presented adding attr3 to Type1 as their only other known solution. Then asked for a solution in an "advanced" way. "advanced" doesn't mean better, so I just provided the remaining alternatives. IMO it does fit the criteria, though really only OP could confirm/deny if it meets their criteria.
Wasn't aware that advanced meant "worse". I'll keep it in mind for the future.
|
0

If you want to get the same result when missing attr3 attribute as with js code you can just cast config type to intersection of types:

interface Type1 {
  attr1: string;
  attr2: string;
}
interface Type2 {
  attr1: string;
  attr2: string;
  attr3: string; // extra attribute
}

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

function fn(config: Type1 | Type2): void {
  // Property 'attr3' does not exist on type 'Type1 | Type2'.ts(2339)
  const { attr1, attr2, attr3 } = config as UnionToIntersection<Parameters<typeof fn>[0]>;
  console.log(attr1);
  console.log(attr2);
  console.log(attr3);
}

fn({ attr1: '', attr2: '' })

or just to ... = config as Type1 & Type2; if you don't mind some inflexibility.

TS playground

Comments

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.