1

So I have ( and loads of other types )

export interface A {
  links: number
  name: string
}

export interface B {
 cat: boolean
 name: string
}

I thought to use

const IsTypeB = (obj: any): obj is B => {
  if ((obj as B).cat) {
    return true
  }
  return false
}

which will work but ... (Motivation: I want it to be scalable as I am planning to write type checking functions for each interface. With the above when I add another interface which has a field cat I'll need to add additional checks to IsTypeWhatever functions, which is going to get messy very quickly.)

So ideally I want something like:

const IsTypeB = (obj: any): obj is B => {
  if (obj is type B (sudo code)) {
    return true
  }
  return false
}

where I don't have to query a specific field. Is this possible?

rose specs
  • 905
  • 8
  • 18
  • Not possible with interfaces, no. Actual classes, yes. Already built in – bryan60 Dec 24 '19 at 14:19
  • Annoyingly this is for JSONs so classes aren't really appropriate :/ – rose specs Dec 24 '19 at 14:39
  • Often ends up being the case. And Sometimes typescript can require a lot of boilerplate. But I’d say your more appropriate question here is Probably why do you need all these type guards and why don’t you already know what interface you’re dealing with so much. Could the application architecture or data model be improved to not require this? – bryan60 Dec 24 '19 at 15:39
  • You have to assume TypeScript types are removed during compilation. So you can't do that unless it's indeed an instance of a Class. Instead, you have to check for fields and such (e.g. `field in obj`) so you can check the actual object. – zeh Dec 24 '19 at 17:02
  • There are many libraries out there dealing with run-time types/validation/narrowing and this question would quickly become very opinionated. So better narrow down your question to a specific library. Regarding TS, a type guard generator remains out of scope, as said [here](https://github.com/Microsoft/TypeScript/issues/15265). You can also have a look at the two mentioned alternatives in the issue as starting point. – ford04 Dec 24 '19 at 23:17
  • Does this answer your question? [Check if an object implements an interface at runtime with TypeScript](https://stackoverflow.com/questions/33800497/check-if-an-object-implements-an-interface-at-runtime-with-typescript) – ford04 Dec 24 '19 at 23:17

1 Answers1

2

There's no nice built-in functionality to do this, as was mentioned in the comments. There are a few approaches, but none of them are great.


Write the checks manually

As you mentioned in your question, you can just write everything manually. I'd do this if you don't have very many interfaces, but it quickly becomes impractical.

Automatically generate boilerplate

One approach is automatically generating the boilerplate. This is less boring than writing it yourself, but still has a lot of problems (do you check in the boilerplate? do you include it in some build step?)

This would take a while to write yourself, but there are npm libraries to do this for you. I haven't used any so I won't make recommendations, but I'm sure you could find some.

Write the interfaces some other way

Instead of using regular typescript interfaces, you could define them some other way to facilitate automatic checking. There are also many libraries that do this, though again I'll make no recommendations.

It's also practical to do this yourself, if your use case isn't too complicated.

I've put together a simple example that will check one level deep for simple types in an object. It won't work for every case, so if you decide to take this approach yourself make sure to design it to work for the cases you care about.

class Checker<T extends {}> {
  constructor(public readonly example: T) {}
  public check(other: {}): other is T {
    const exampleKeys = new Set(Object.keys(this.example));
    const otherKeys = new Set(Object.keys(other));
    if (exampleKeys.size !== otherKeys.size) { return false; }
    for (const key of exampleKeys) {
      if (!otherKeys.has(key)) { return false; }
      if (typeof (other as any)[key] !== typeof (this.example as any)[key]) { return false; }
    }
    return true;
  }
}

const bChecker = new Checker({
  cat: true,
  name: "",
})
type B = typeof bChecker.example;

const something: {} = {
  cat: false,
  name: "Spot",
}

if (bChecker.check(something)) {
  const b: B = something;
}

Overall, there's no perfect approach. However, there are alternatives to writing a bunch of boilerplate yourself.

Half
  • 5,078
  • 2
  • 10
  • 20