0

This is more of a general practices question.

The language I am working with is Javascript. I have a function that is getting an object with many variables (okay, just 10 variables). What is the best way to make sure this function is getting all the required variables and that they are all set?

I know, I know, why not just use a if statement. That's a big chunk of if statements! As I grow as a programmer, I know that may not be the best method for this. I'm looking for a shortcut actually. How would you check a large sum of variables for existence and non-blank values?

Kyle Hotchkiss
  • 10,754
  • 20
  • 56
  • 82

4 Answers4

1

This is a pretty neat way of handling validation, I usually use this when checking for required fields in form inputs.

var theObj = { /* object loaded from server */ }

function checkTheObj(testObj)
{
  var requiredKeys = ['key1', 'key2', 'key3'];
  for(var keyPos = 0; keyPos < requiredKeys.length; keyPos++)
  {
    if(typeof(testObj[requiredKeys[keyPos]]) == 'undefined')
    {
      return false;
    }
  }
  return true;
}

if(checkTheObj(theObj))
{
  //do stuff
}

You can of course tweak this to return or throw an exception telling the first missing field (or use an internal array to return a list of all missing fields).

Bob Davies
  • 2,229
  • 1
  • 19
  • 28
  • That's pretty much the same use case I have! Thanks for sharing your real-world code. – Kyle Hotchkiss Jun 30 '12 at 16:13
  • `for`-`in` iterates only over *enumerable* properties, in *arbitrary* order, and it iterates over *inherited* properties as well. Properties may have the `undefined` value. – PointedEars Jun 30 '12 at 16:15
  • @PointedEars: The `for-in` is iterating over requiredKeys, not over testObj. It would of course be neater (and correct) to use `for(i=0;i – Bob Davies Jun 30 '12 at 16:20
  • `requiredKeys` refers to an `Array` instance. AISB, `Array` instances inherit from `Array.prototype`. *Never* use `for`-`in` iteration with `Array` instances. – PointedEars Jun 30 '12 at 16:23
  • Supplemental: The reason for not using `for`-`in` with `Array` instances is not only inheritance. You cannot assume that index properties are enumerable, and iterated in order with `for`-`in`. Because `for`-`in` has no specified iteration order, only "next enumerable property of the object". And an object is specified as an *unordered* collection of properties. Anything else is implementation-dependent, so not compatible. – PointedEars Jun 30 '12 at 16:27
1
function objectHas(obj, properties) {
    var len = properties.length
    for (var i=0; i<len; i++) {
        if (i in properties) {
            if((!obj.hasOwnProperty(properties[i])) || (!obj.propertyIsEnumerable(properties[i]))) {
                return false;
            }
        }
    }
    return true;
}

Usage:

if(objectHas(user, ["email", "password", "phone"])) {
    console.log("awesome");
}

It's simple, but does the job.

Edit: On an ideal world you could extend the Object prototype for an even neater syntax such as if(object.has(["a", "b", "c"])), but apparently extending the Object prototype is the incarnation of evil so a function will have to do :)

Community
  • 1
  • 1
Mahn
  • 16,261
  • 16
  • 62
  • 78
  • In my case, it's in node, but I could see how extending that in a browser would make for a hard day. – Kyle Hotchkiss Jun 30 '12 at 14:32
  • `for`-`in` iterates over *enumerable* properties only, and also over *inherited* ones. This means that this will return `false` whenever you hit an inherited property, which you certainly will because of `for`-`in`. – PointedEars Jun 30 '12 at 16:11
  • Not fixed, this will still return false when it hits an inherited property. Hm. – Mahn Jun 30 '12 at 16:39
  • You need to filter the properties of `properties` first. That is why I suggested, in essence, `Object.keys(properties)` and/or a classic `for` loop instead of `for`-`in`. – PointedEars Jun 30 '12 at 16:45
  • And updated again. I went for a standard for loop since it should be the most compatible option across browsers. It seems to be working well from what I've tested, but let me know if you spot something else. – Mahn Jun 30 '12 at 18:02
1

First of all, you need to improve your understanding of these languages and learn the correct terminology.

  1. There is no (single) language named "Javascript" at all. You are implicitly using several languages here (depending on the runtime environment), all of which are ECMAScript implementations, and one of which is Netscape/Mozilla JavaScript (in Mozilla-based software like Firefox).
  2. An object does not have variables, it has properties (not: keys). Global code, function code, and eval code can have variables; that is a different (but similar) concept.
  3. The function is not getting an object, it is being passed a reference to an object as argument.

As a programmer, you should already know that you can do repetitive tasks in a loop; the associated statements in ECMAScript implementations are for, for-in, while and do. So you do not have to write several if statements.

You can access the properties of an object in two ways, where property is the property name:

  1. Dot notation: obj.property
  2. Bracket notation: obj["property"]

The second one is equivalent to the first if the property name is an identifier, i.e. if it follows certain naming rules. If the property name is not an identifier or if it is variable, you have to use the second one. This also shows that all property names are string values. So you can store the name of a property as value of a variable or another property, and then access the variable or property in the property accessor. In the following, a property name (property) is stored in and used from a variable:

var propertyName = "property";
obj[propertyName]

Combining that with a loop allows you to iterate over certain properties of an object. Unfortunately, the solutions presented so far are flawed in two respects: A for-in statement iterates only over the enumerable properties of an object, and it does so in arbitrary order. In addition, it also iterates over the enumerable inherited properties (which is why one solution requires the hasOwnProperty() call).

A simple, sure and efficient way to iterate only over certain properties of an object in a defined order looks as follows:

var propertyNames = ['name1', 'name2', 'name3'];
for (var i = 0, len = propertyNames.length; i < len; ++i)
{
  /* … */ myObject[propertyNames[i]] /* … */
}

This works because propertyNames refers to an Array instance, which encapsulates an array data structure. The elements of an array are the properties of the Array instance that have integer indexes from 0 to 65535 (232−1). Because indexes are not identifiers (they start with a decimal digit), you have to use the bracket property accessor syntax (some people misunderstand this and refer to all ECMAScript objects as "arrays", even call them "associative arrays" and […] the "Array operator"). Therefore, propertyNames[i] evaluates to the values of the elements of the array in each iteration as i is increased by 1 each time. As a result, myObject[propertyNames[i]] accesses the property with that name in each loop.

Now, to find out whether the property is set, you need to define what that means. Accessing a property that does not exist results in the undefined value (not in an error). However an existing property may also have the undefined value as its value.

If "not set" means that the object does not have the property (but may inherit it), then you should use hasOwnProperty() as used in Mahn's solution.

If "not set" means that the object does not have the property and does not inherit it, then you should use the in operator, provided that the object is not a host object (because the in operator is not specified for them):

if (propertyNames[i] in obj)

If "not set" means that the object either has or inherits the property, but the property has the undefined value, or the object neither has nor inherits the property, then you should use the typeof operator as used in Bob Davies' and aetaur's solutions (but the latter approach using Array.prototype.every() is less compatible as-is; that method was not specified before ECMAScript Edition 5, and is not available in IE/JScript < 9).

There is a third option with ECMAScript Edition 5.x, the Object.keys() method which (despite its name) returns a reference to an Array instance that holds the names of all not-inherited properties of the argument:

var propertyNames = Object.keys(obj);

/* continue as described above */

It is a good idea to emulate Object.keys() if it is not built-in, as this algorithm is frequently useful.

PointedEars
  • 14,752
  • 4
  • 34
  • 33
  • @KyleHotchkiss Certainly not. That is where most of the nonsense in the other answers comes from. – PointedEars Jun 30 '12 at 16:13
  • How come this answer was downvoted? It does address the question and gives background to back it up. – Mahn Jun 30 '12 at 18:17
  • @Mahn ISTM that the reason is that the OP is not well-versed in these languages, therefore asked the wrong question (or the question wrong), did not understand or even bother to read the answer, or read the answer and misunderstood it, and is taking the simpler (but wrong) approach of cargo-cult coding instead. I have seen it several times before. – PointedEars Jul 01 '12 at 09:21
  • I thought that the answer @aetaur worked best for my needs. Am I well-versed? No. I'm a 20 year old who has never formally been taught programming, like many others. – Kyle Hotchkiss Jul 03 '12 at 21:56
  • @KyleHotchkiss One wonders then, why do you think you are in a position to judge whether Crockford's book is any good, or comparing it to my answer, with bias towards the former? I have not quoted anything from any book in my answer, it is based on more than a decade of experience. – PointedEars Jul 04 '12 at 03:27
0

This expression returns true, if all variables from variableNameList (list of required variable names) set in object o:

variableNameList.every(function(varName){ return typeof o[varName] !== 'undefined'; });

You can use underscore _.all function instead of native every, and underscore _.isUndefined instead of typeof ....

aetaur
  • 336
  • 1
  • 3
  • 8
  • 2
    `Array.prototype.every` requires a sufficiently recent implementation that provides this ECMAScript Edition 5 method, or an emulation thereof. The dot notation will not work as intended by you, and properties may have the `undefined` value. – PointedEars Jun 30 '12 at 16:08
  • Totally fine though - this is exactly the shorthanded-ness I was looking for. – Kyle Hotchkiss Jun 30 '12 at 16:12
  • @KyleHotchkiss If you only care about the very latest major implementations/browsers, you should have said so and spared everyone the trouble writing replacement code. – PointedEars Jun 30 '12 at 16:20
  • @PointedEars That's why i mentioned underscore.js. Thanks for your dot notation comment, fixed :) – aetaur Jul 01 '12 at 07:41
  • @aetaur It was/is not obvious from your posting that you mean underscore.*js* (which I had not heard of). I do not think underscore.js is a good idea; it is better to augment native objects (provided that does not interfere with inheritance) so that you do not have to rewrite your code when the features in question become ubiquitous and the compatibility library becomes obsolete. As for my comment, you are welcome. – PointedEars Jul 01 '12 at 09:14
  • @aetaur What strikes me as particularly disturbing is the misguided notion that it was better to use an `_.isUndefined()` function call than the `typeof` operator. It certainly is not. Not only are (unoptimized) function calls more expensive than operations, but also an `_.isUndefined()` function is *useless* if the argument is an identifier that is truly undefined, because a `ReferenceError` exception will be thrown before control even enters the function. Not so with the `typeof` operator. – PointedEars Jul 01 '12 at 09:27