Home > Software design >  Typescript running same method on diffrent object types
Typescript running same method on diffrent object types

Time:12-03

I want to create function that accepts obj and value as arguments and runs objects method add with passed value. Unfortunetly obeject can be either typeof A or B. Depending on object type method add accepts string or number. How can i achive that?

class A {
  add(a: number) {
    return 10   a
  }
}

class B {
  add(a: string) {
    return 'ten'   a
  }
}

I tried doing something like that but typescript lints it (and I understand that).


const runObejctMethod = (obj: A | B, value: string | number) =\> {
obj.add(value)
}

error:


TS2345: Argument of type 'string | number' is not assignable to parameter of type 'never'.   Type 'string' is not assignable to type 'never'

CodePudding user response:

To create a function that can accept objects of different types and run their add method with a passed value, you can use type guards to check the type of the object and then call the appropriate method.

Here is an example of how you can implement this function:

class A {
  add(a: number) {
    return 10   a;
  }
}

class B {
  add(a: string) {
    return "ten"   a;
  }
}

function runAdd(obj: A | B, value: any) {
  if (obj instanceof A || ) {
    return obj.add(value);
  } else if (obj instanceof B) {
    return obj.add(value);
  }
}

const a = new A();
const b = new B();

console.log(runAdd(a, 5));  // Output: 15
console.log(runAdd(b, "hello"));  // Output: "tenhello"

In this example, the runAdd function accepts an object obj of type A or B and a value value of any type. It then uses a type guard to check the type of obj and calls the appropriate add method with the passed value. You can then use this function to call the add method of an object of type A or B with any type of value.

CodePudding user response:

You can try using type guards to determine the type of the object and then call the correct method. You can do this by checking the type of the object with the typeof operator, or by checking for the existence of a specific property on the object.

Here's an example using the typeof operator:

const runObejctMethod = (obj: A | B, value: string | number) => {
  if (typeof obj === 'A') {
    obj.add(value as number);
  } else {
    obj.add(value as string);
  }
}

In this example, we use the typeof operator to check the type of obj. If it's an instance of A, we call the add method with a value that has been explicitly typed as a number using the as keyword. Otherwise, we call the add method with a value that has been explicitly typed as a string.

You can also use a type guard by checking for the existence of a specific property on the object. For example, you could add a property called isA to the A class and use it in a type guard like this:

class A {
  isA = true;
  add(a: number) {
    return 10   a
  }
}

class B {
  add(a: string) {
    return 'ten'   a
  }
}

const runObejctMethod = (obj: A | B, value: string | number) => {
  if (obj.isA) {
    obj.add(value as number);
  } else {
    obj.add(value as string);
  }
}

In this example, we check for the existence of the isA property on the obj object. If it exists, we know that obj is an instance of A, and we call the add method with a value that has been explicitly typed as a number. If the isA property doesn't exist, we know that obj is an instance of B, and we call the add method with a value that has been explicitly typed as a string.

CodePudding user response:

You may use generics

const runObejctMethod2 = <C extends {add(v: any): any}>(obj: C, value: Parameters<C['add']>[0]) => {
    obj.add(value)
}

There are other methods (like overloaning), but they may require casting with as or long generics

CodePudding user response:

You can use a generically constrained function signature to accept an object whose add method is compatible with the constraint.

You can also define the compatibility of the constraint from specific classes that you choose by using a type utility.

I've explained this more in the comments in the code below:

TS Playground

class A {
  add (value: number): number {
    return 10   value;
  }
}

class B {
  add (value: string): string {
    return 'ten'   value;
  }
}

class C {
  add (value: { num: number }): { num: number } {
    return { num: 10   value.num };
  }
}

function runObejctMethod <T extends Parameters<(A | B)['add']>[0]>(
  obj: { add: (value: T) => T },
  value: T,
): void {
  const result = obj.add(value);
  console.log(result);
}

// The constraint expression above "Parameters<(A | B)['add']>[0]" means:
// "a union of the type of the first parameter of A.add or B.add",
// so — in this case — it means "string | number":
type Constraint = Parameters<(A | B)['add']>[0];
   //^? type Constraint = string | number

// You could add more compatible classes to the union
// to extend the accepted types in the constraint,
// for example: Parameters<(A | B | C | D)['add']>[0]

// --------------------

// Usage:

const a = new A();
const b = new B();
const c = new C();

// Ok:
runObejctMethod(a, 1); //=> Logs: 11
runObejctMethod(b, ' thousand'); //=> Logs: "ten thousand"

// Not ok (as expected):
runObejctMethod(a, ' thousand'); /*
                   ~~~~~~~~~~~
Argument of type 'string' is not assignable to parameter of type 'number'.(2345) */

runObejctMethod(b, 1); /*
                   ~
Argument of type 'number' is not assignable to parameter of type 'string'.(2345) */

runObejctMethod(c, { num: 1 }); /*
                ~
Argument of type 'C' is not assignable to parameter of type '{ add: (value: string | number) => string | number; }'.
  Types of property 'add' are incompatible.
    Type '(value: { num: number; }) => number' is not assignable to type '(value: string | number) => string | number'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'string | number' is not assignable to type '{ num: number; }'.
          Type 'string' is not assignable to type '{ num: number; }'.(2345) */


c is not a compatible object type, but if you had included it in the original constraint, it would be allowed:

TS Playground

class C {
  add (value: { num: number }): { num: number } {
    return { num: 10   value.num };
  }
}

function runObejctMethod <T extends Parameters<(A | B | C)['add']>[0]>(
  //                                                    ^
  //                                     C is now included in the union
  obj: { add: (value: T) => T },
  value: T,
): void {
  const result = obj.add(value);
  console.log(result);
}

const c = new C();
runObejctMethod(c, { num: 1 }); // Ok
  • Related