2

I have

interface A {
  a_id: string;
  name: string;
}

interface B {
 b_id: string;
 name: string;
}

Now,I want to check an object say:

const myObj : A|B = {b_id: '1', name: 'one'};

How to check the type and determine whether it belongs to A or B ?

I tried few links but shouldn't it be more easier to get the type ?

Samuel
  • 1,128
  • 4
  • 14
  • 34
  • You may find [this](https://stackoverflow.com/questions/14425568/interface-type-check-with-typescript) interesting. – Ivan Jan 08 '20 at 15:08
  • @Ivan Your link also says, there no way to check an interface at runtime (what I was thinking). – crownlessking Jan 08 '20 at 15:14

2 Answers2

1

Update: the question as stated doesn't indicate that the request is to be able to distinguish the type without checking properties. Unfortunately, that is impossible; TypeScript compiles to JavaScript, which is what actually runs. And TypeScript's type system, where interfaces like A and B are found, is completely erased when the code is compiled. So there is no A or B at runtime to check.

If you want to be able to distinguish, at runtime, whether myObj is A or B, you need to write JavaScript code that does this... such as checking properties. What you can do is use TypeScript to help you write this code and give it typings so that the compiler also understands that your property check narrows an A | B to an A or a B. And that's what the rest of this answer (and the other question's answer) tells you. Good luck!


The other question's answer suggests that you write a user-defined type guard function, which will definitely work; the idea is that you tell the compiler what you consider to be a good way to distinguish between A and B, and it will let you use that function to do so.

An easier way to do it in your case is to use the in operator, which can be used as a type guard (since TypeScript 2.7). For example:

declare const obj: A | B;
if ("b_id" in obj) {
  // obj is a B in here
  console.log(obj.name.toUpperCase());
} else {
  // obj is an A in here
  console.log(obj.a_id.toUpperCase());
}

Note that this is technically unsafe, since it's possible to have an object of type A | B which isn't distinguishable that way:

function getEvilAorB(): A | B {
  interface EvilA extends A {
    b_id: number;
  }
  const evilA: EvilA = { name: "", a_id: "", b_id: 123 };
  return evilA; // okay, EvilA extends A extends A | B
}

Notice that getEvilAorB() returns a valid A, which is therefore a valid A | B, but unfortunately it will fail the above test because of the numeric name property:

const evilA = getEvilAorB();
if ("b_id" in evilA) {
  console.log(evilA.b_id.toUpperCase()); // compiles but  at runtime
  // TypeError! evilA.name.toUpperCase is not a function
}

If you aren't concerned about such edge cases, then the in type guard is probably sufficient. Otherwise you should consider either writing a user-defined type guard, or making your interfaces members of a discriminated union which is a conventional and supported way to have the compiler distinguish between union members.

Hope that helps; good luck!

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Hi, I made one correction in interface `A`. It was a typo. Sincere apologies. I want to ignore checking each properties to determine the type . Thanks – Samuel Jan 09 '20 at 06:18
1

for me is easiest way to use discriminant if you are able to change shape of these interfaces.

interface A {
  discriminantKey: 'interfaceA';
  a_id: string;
  b_id: string;
}

interface B {
 discriminantKey: 'interfaceB';
 b_id: string;
 name: string;
}

and when you ask on your discriminant key in swithc case you will have type guard even in typescript not only runetime.

const myType = {...}
switch(myType.discriminantKey) {
  case 'interfaceA':
    // here is type infered to 'A' interface;
   break
  case 'interfaceB':
    // here it is 'b'
}

playground

Juraj Kocan
  • 2,648
  • 1
  • 15
  • 23