0

Normally, to determine variable type in TypeScript, I can do something like this:

if (typeof payload === 'string') {
    console.log(payload) // const payload: string
}

But this one doesn't work:

if (typeof payload === 'object') {
    console.log(payload) // const payload: any
}

Here's typescript playground with some non-working examples I ended up

Question:

How do I make a type guard for an object?

Question*:

How do I make a type guard for a specific shape of object?

Jerry Green
  • 1,244
  • 1
  • 15
  • 25
  • Does this answer help? https://stackoverflow.com/a/14706877/1024832 The first comment on that particular answer may be enlightening too. – Marc Nov 22 '20 at 06:43
  • Hi Jerry, I may be missing something but your code on typescriptlang.org/play seems to work as expected, no? – Jacopo Lanzoni Nov 22 '20 at 07:47
  • @JacopoLanzoni `payload !== null && typeof payload === 'object'` check doesn't seem to work as "expected", it seems to be a sufficient check to ensure a variable is an object, but maybe I'm missing something, since TS doesn't count on that. Anyway, this answer looks pretty good actually: https://stackoverflow.com/a/64951336/3720305, and may easily cover specific shapes of objects, though for a simple cases with "just an object" a function definition seems a bit excessive – Jerry Green Nov 22 '20 at 08:01

3 Answers3

4

First of all, object can mean a lot in JavaScript. It's like any. For example null and arrays are also objects, when using typeof.

In TypeScript you should prefer using e.g. Record<string, unknown> over object. In this example unknown or whatever you want. Record<key, value>. It's compatible with object and better to debug. (docs).

Here is an example to check for an object, which is not null or array.

const isObject = (input: unknown): input is Record<string, unknown> => {
  return typeof input === 'object' && input !== null && !Array.isArray(input);
};

But keep in mind that object validation is not trivial (in any language). It also depends on your case. For example if you want to check for an instance of a class you could use instanceof. ...

Domske
  • 4,795
  • 2
  • 20
  • 35
3

Note that part of your problem is based on the usage of any. This type is not recommended for use in new code since Typescript 3.0, which introduced the type unknown - see this question for the example.

Applied to your case, we can easily check that just replacing any with unknown in the initial variable definition makes things much better:

const payload: unknown = { a: 1 }

if (typeof payload === 'object') {
    // This type guard works, but narrows to object | null, since typeof null === 'object'
    console.log(payload) // const payload: object | null
}

if (payload !== null && typeof payload === 'object') {
    // This one narrows to object | null too; this looks like a quirk in the algorithm
    console.log(payload) // const payload: object | null
}

if (typeof payload === 'object' && payload !== null) {
    // This one works, since payload is first narrowed to object | null, and then you explicitly exclude null
    console.log(payload) // const payload: object
}

if (payload instanceof Object) {
    // This one works, too
    console.log(payload) // const payload: object
}

Playground Link

Cerberus
  • 8,879
  • 1
  • 25
  • 40
  • That's absolutely good answer, thank you! Though I expressively set `any` because it's what I get from a library, and I can't change that. Good to know about `unknown`! (I might cast any to unknown though...) – Jerry Green Nov 22 '20 at 08:12
  • 1
    I suppose since this doesn't answer how to ensure the object has a specific shape, and it works only for plain objects, another answer deserves to be "accepted" just a bit more: https://stackoverflow.com/a/64951336/3720305 (since more universal). Anyway, thanks for the insight again: very good precaution – Jerry Green Nov 22 '20 at 08:33
  • Well, since anything is assignable to `unknown`, you always can just assign incoming `any` to your own `unknown`, without any casts (pun unintended). – Cerberus Nov 22 '20 at 08:55
0

Just make a regular typeguard?

if (isObject(payload)) {
    console.log(payload)
}

function isObject(payload: any): payload is object {
    return typeof payload === "object"
}
Rubydesic
  • 3,386
  • 12
  • 27
  • May easily cover specific shapes of objects (which is good), though for a simple cases with "just an object" a function definition seems a bit excessive, but it seems it's the only way. I didn't know about "variable *is* type" syntax. Thanks! – Jerry Green Nov 22 '20 at 08:04
  • This is not good. For example `null` and array are also objects. You have to check for null, array and maybe more. And you should never use `object` as type. It's like `any`. Use e.g. `Record` (`unknown` or whatever). It's compatible to `object`. – Domske Jun 28 '21 at 10:24