0

NOTE: I realize this question is extremely similar to this thread but it does not apply to Classes and, since classes cannot extend an interface, I am a bit stuck here.

I ran into an interesting question I would like to solve for my own learning. I am trying to type my class so that one of two properties is required for a transaction. In this specific case, I need either bankAccountNumber or encryptedBankAccountNumber present.

export class AchDetails {
    'bankAccountNumber'?: string;

    'encryptedBankAccountNumber'?: string;
   
    'type'?: AchDetails.TypeEnum;

    static discriminator: string | undefined = undefined;

    static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [
        {
            "name": "bankAccountNumber",
            "baseName": "bankAccountNumber",
            "type": "string"
        },
        {
            "name": "encryptedBankAccountNumber",
            "baseName": "encryptedBankAccountNumber",
            "type": "string"
        }   
        ];

    static getAttributeTypeMap() {
        return AchDetails.attributeTypeMap;
    }
}

export namespace AchDetails {
    export enum TypeEnum {
        Ach = 'ach',
        AchPlaid = 'ach_plaid'
    }
}

I wanted to use the solution from the issue linked above, but this does not help as (from what I understand) the Class can't use external typings:

interface AchBaseDetails {
    'bankAccountNumber'?: string;
    'encryptedBankAccountNumber'?:string;
}


type RequireBankNumberType<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

export type RequiredAchDetails = RequireBankNumberType<AchBaseDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'>

Is there any way to do this so that it will work with the Class in this instance?

Michael Ossig
  • 205
  • 2
  • 9
  • 1
    Does this answer your question? [Constructor overload in TypeScript](https://stackoverflow.com/questions/12702548/constructor-overload-in-typescript) – Jared Smith Jun 07 '23 at 17:26
  • You can’t do this with a `class`, (and you can’t do it with an `interface` either); it would need to be a union, and classes can’t be unions. You’ll need to refactor somehow; could you [edit] to provide test cases for how it should be used? (Are the properties ever modified after being set? Where are they set? (I don’t see anything happening in your constructor) Relevant use cases will drive how the refactoring should go. – jcalz Jun 07 '23 at 17:29
  • 1
    @JaredSmith definitely going to look into this. I will update with how it works! – Michael Ossig Jun 07 '23 at 17:34

1 Answers1

0

You could take an argument to the constructor which follows the pattern mentioned in the linked question.

type Encrypted = {encrypted: string}
type Unencrypted = {notEncrypted: string}
type AccNumber = Encrypted | Unencrypted;


function isEncrypted(acct: AccNumber): acct is Encrypted {
  return typeof (acct as Encrypted).encrypted === 'string';
}

class Account {
  public constructor(private acct: AccNumber) {}

  getBalance(): number {
     if (isEncrypted(this.acct)) {
         return getBalanceFromNotEncrypted(this.acct.encrypted);
     } else {
         return getBalanceFromEncrypted(this.acct.notEncrypted);
     }
  }
}


const acc = new Account({encrypted: 'blah'});
const acc2 = new Account({notEncrypted: 'blah'});
// Does not compile
const badAccount = new Account({x: 12, encrypted: 'test'});
// Does not compile
const badAccount2 = new Account({});

See TS playground example

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217