Hacker News new | ask | show | jobs
by EnderShadow8 1709 days ago
Note: don't use typeof x === 'object' to check whether something is a valid object, because it will return true for arrays as well.

Arrays are objects, so this is expected behaviour.

6 comments

And it will return true for null as well ;)
Did anybody else have their brain do a weird backflip seeing `Array.isArray[0]`??

  Object.getOwnPropertyDescriptors(Array.isArray)
  /* 
  {
    '0': {
      value: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray',
      writable: true,
      enumerable: true,
      configurable: true
    },
    length: { value: 1, writable: false, enumerable: false, configurable: true },
    name: {
      value: 'isArray',
      writable: false,
      enumerable: false,
      configurable: true
    }
  }
  */
How did you get that result? I only see length and name.
Well, it was a kind of a joke intended for the folks whose brain saw the comment the same way I did. I got the result by applying the footnote to the object, as half-suggested by syntax of the parent:

  Array.isArray[0] = urlString
The core problem here is that you don't actually want to check if something is an object, but whether it matches the Human type. The correct way to do that is to define a type guard [0], for example:

  function isHuman(input: any): input is Human {
    return (
      Boolean(input) &&
      Object.prototype.hasOwnProperty.call(input, "name") &&
      Object.prototype.hasOwnProperty.call(input, "age")
    );
  }
There are libraries which can automate this for you which is the route I would recommend if you need to do this often. As you can see, the code to cover all edge cases such as `Object.create(null)` etc is not trivial.

[0] https://www.typescriptlang.org/docs/handbook/advanced-types....

Your input parameter type should be unknown, not any.
That's fairly subjective and I can see arguments for both sides, in the above example I've gone with the approach the Typescript documentation itself gives which uses any. Given the parameter is only used as an input, using any and unknown are interchangeable and there is no difference in this specific case.
I somewhat agree, but to me the semantics using unknown makes more sense as the function is attempting to answer a question of the input object.

"What is object? I don't know. Does it meet these conditions? Yes then object is Human else not."

While I see where you're coming from, semantic formulation like this is highly subjective. All the same the problem statement could be "Given any kind of input, tell me whether it's a Human or not".
This approach is "correct" only when you're interacting with an API you have no control over whatsoever because specifying all this and maintaining it is really error prone.

All this boilerplate is not a proof. It remains an assertion. And so in most cases you might as well just add a "type"/"kind" property to your object (or create a class).

> There are libraries which can automate this for you which is the route I would recommend

Which libraries do you recommend? I've had to do this a couple of times in my current project and it's painful (and I made mistakes).

Personally I really enjoy Typanion [0] since it's very similar to Yup [1] which I previously had extensive experience with. You can find more alternatives and a lengthy discussion about the whole problem space and its history in [2].

[0] https://github.com/arcanis/typanion

[1] https://github.com/jquense/yup

[2] https://github.com/microsoft/TypeScript/issues/3628

Note: please post a better solution.
I very, very much disagree with the type of suggestions here. Probably because I don't know TS but here it comes anyways...

You don't need a library or check that it isn't an array or anything like that. What you actually want to check is what it _is_.

You absolutely don't care whether that object is an array or a function or what have you. What you care about in that piece of code is that it satisfies what you want to do with it.

In the context of the article you can do an ad-hoc check on the fields that you require in that context.

Aside:

There are places where you want to have a kind of rigidity around the shape of your objects, including homogeneous collections. The JIT might reward you with optimizations in certain cases. But that is optimization, so you are supposed to measure first and only then apply them or have a very clear picture of how your runtime behavior will be.

Probably best to just lift one off of a major library like Lodash, they're well tested and efficient (no need to actually use the library, do include the LICENSE somewhere though):

https://github.com/lodash/lodash/blob/master/isObject.js

But depends on your needs, and you can also attach a typeguard to it.

Why not use the library? With tree shaking and/or direct imports you will ensure the same bundle size as if you just copied the file, and you don't have to worry about licenses etc. In fact, since other dependencies might depend on lodash you can deduplicate the import and actually save on bundle size.

You'll also get notified of any security issues in your lodash imports if your CI pipeline is setup for doing that kind of thing.

Mostly if you look at it out of the context of a single function - a lot of projects end up taking a huge number of dependencies, with a lot of overlapping functionality, because you used one function from this one, another function from that one… I’m fine with using libraries when they actually do heavy lifting that’s core to an application, but a single two line function requiring including hundreds of unrelated irrelevant ones? That will impact the coding style of your team and does have security downsides, like needing to trust the library authors and potentially breaking your build because they changed their APIs or deleted a package or whatever. Copying a 2 line function has very clear boundaries to what it can and can’t do, and doesn’t hide the internals of what you’re doing behind the mystique of “an external dependency”.
If you want to avoid a library then you can do `typeof x === "object" && !Array.isArray(x) && x !== null`. Not ideal, but you can of course turn it into a utility function.
x && !Array.isArray(x) && typeof x === 'object'.

its typical to start off with `var &&` to avoid operations on null & undefined values

For environment lacks Array.isArray method i think this works well

!!(x && x.__proto__ === [].__proto__)

a simple solution would be

> if(entry && entry.constructor === Object){}

This isn't a great solution:

    entry = Object.create(null);
    entry.name = "Jason";
    entry.age = 42;
    entry.constructor === Object // false - constructor is undefined.
`entry` is a valid Human here, but fails your check. Actually, just creating a new class that implements the Human interface will cause a similar problem, since the constructor will be the class instead of Object. You don't even really want to exclude arrays here:

    entry = []
    entry['name'] = "Jason";
    entry['age'] = 42;
Again, entry is a valid Human here.
I'm having some trouble finding your proposal in the thread... how would you do it?
:) Fair enough:

    function isHuman(obj: unknown): obj is Human {
      return !!obj && 
        typeof obj === 'object' &&
        typeof (obj as any).name === 'string' &&
        typeof (obj as any).age === 'number';
    }
This checks the shape of the object, and returns true if it's a `Human`. This will work for array or objects or classes or anything.
https://javascriptweblog.wordpress.com/2011/08/08/fixing-the...

tl;dr: Object.prototype.toString.call, you most likely to also want to exclude null, RegExp, Function, Date, Number, Boolean & String (not string)

You're absolutely right. Should have stated that I meant a plain object (key-value-pair). But either didn't want to focus on this topic for that post because it would have been just a bit too much to talk about :D
How about

any => Object.prototype.toString.call(any).slice(8, -1)

I really like this package, because it not only uses the method you mentioned for checking types, but is written in TS, so you get the type checking feedback.

https://github.com/sindresorhus/is

or even this

typeName = Object.prototype.toString.call

bit more self-explanatory `it => Object.prototype.toString.call(it).match(/^\[object (\w+)]/).pop()`