Home > database >  How to access a property that only exists on one of my two return types?
How to access a property that only exists on one of my two return types?

Time:10-18

I have a function being called:

const accountDetails: IAccount | ISmallError = getAccount()

It might return IAccount or ISmallError.

On the next line I want to do this:

if (accountDetails.error) return response.json({ error: error.error });

But under .error it says

Property 'error' does not exist on type 'IAccount | ISmallError'.
  Property 'error' does not exist on type 'IAccount'.ts(2339)

what do I do about this?

One solution that does work is:

const err = accountDetails as ISmallError;
if (err.error) return response.json({ accountDetails: "No account found" });

but is this the best way?

CodePudding user response:

Assuming the property is meant to be a discriminant property something as simple as 'error' in accountDetails will be sufficient:

type IAccount = { payload: 2 }
type ISmallError = { error: Error }
declare const getAccount: () => IAccount | ISmallError;

const accountDetails: IAccount | ISmallError = getAccount()

if ('error' in accountDetails) {
  accountDetails.error // No longer an error
}

Using a property existence check is better (in my opinion) because it uses the type system instead of overriding it (as the cast does), ensuring we will get an error in more circumstances in future.

Even better would be to introduce a proper discriminant property to IAccount and ISmallError so that you can switch on the tag. That helps avoid the case where IAccount grows its own error field in the future (fewer places for collisions makes for an easier-to-maintain codebase):

type IAccount = { type: 'accountDetails', payload: 2 }
type ISmallError = { type: 'prodError', error: Error }

// ... snip ...

if (accountDetails.type === 'prodError') {
  // Full narrowing here and no potential overlapping fields later
}
  • Related