3
\$\begingroup\$

I have got a an object that has values true and false for certain keys.I need to find which keys has got a boolean value and convert them to 1 and 0 respectively.Please find the code below.I wanted to check for keys in an object that has got boolean values and then convert them to numbers.Im a noob in Javascript and i got some help from fellow programmers in SO.This works but im sure there is a better way of doing this.

    var obj ={
    "label":'test',
    "tel":123456,
    "settings": {
      "playback": false,
      "attachfile": true,
    },
    "setTime":true,
    "setDay":false,
    "arrSettings" :[
        'Testing',
        {"playback": false,"attachfile": true},
        {"playback": false,"attachfile": true},
        123456789
    ]
}


function changeBooleanToNumber(o){

    for(var key in o){

        if(/^boolean$/i.test(typeof o[key])){
            o[key] = Number(o[key]);
        }

        if (Object.prototype.toString.call(o[key]) === '[object Object]'){

           changeBooleanToNumber(o[key]);     

        }
        else if( Object.prototype.toString.call( o[key]) === '[object Array]' ) {

            for(var i=0;i<=o[key].length-1;i++){

                if(Object.prototype.toString.call(o[key][i]) === '[object Object]'){
                     changeBooleanToNumber(o[key]);
                }

            }

        }

   }
}

changeBooleanToNumber(obj);
\$\endgroup\$
2
\$\begingroup\$

Before I do anything else I feel compelled to clean up your spacing convert " to ' to consistantly use only one type of strings

var obj = {
    'label': 'test',
    'tel': 123456,
    'settings': {
        'playback': false,
        'attachfile': true,
    },
    'setTime': true,
    'setDay': false,
    'arrSettings': [
        'Testing',
        {'playback': false, 'attachfile': true},
        {'playback': false, 'attachfile': true},
        123456789
    ]
};

function changeBooleanToNumber(o) {
    for (var key in o) {
        if (/^boolean$/i.test(typeof o[key])) {
            o[key] = Number(o[key]);
        }

        if (Object.prototype.toString.call(o[key]) === '[object Object]') {
            changeBooleanToNumber(o[key]);
        } else if (Object.prototype.toString.call(o[key]) === '[object Array]') {
            for (var i = 0; i < o[key].length - 1; i++) {
                if(Object.prototype.toString.call(o[key][i]) === '[object Object]') {
                    changeBooleanToNumber(o[key]);
                }
            }
        }
    }
}

changeBooleanToNumber(obj);

Now:

  1. Use +x instead of Number(x); it is shorter and faster in all cases I've ever bothered to test.

  2. Don't use var inline, javascript doesn't have block scope (only function scope) so it is better to declare all necessary variables at the start of your functions.

  3. Cache Object.prototype.toString to make code more readable.

  4. Use toString instead of a regex test for checking for the bool type

  5. simplify for loop and store the upper bound in a variable instead of computing it every time

  6. Use a hasOwnProperty check on the for in loop.

This leaves me with this:

function changeBooleanToNumber(o) {
    var toString = {}.toString,
        hasOwn = {}.hasOwnProperty,
        key, 
        i,
        len;
    for (key in o) {
        if (hasOwn.call(o,key)) {
            if (toString.call(o[key]) === '[object Boolean]') {
                o[key] = +o[key];
            } else if (toString.call(o[key]) === '[object Object]') {
                changeBooleanToNumber(o[key]);
            } else if (toString.call(o[key]) === '[object Array]') {
                len = o[key].length;
                for (i = 0; i < len; i++) {
                    if(toString.call(o[key][i]) === '[object Object]') {
                        changeBooleanToNumber(o[key]);
                    }
                }
            }
        }
    }
}

This resulting code looks to me like it has too many indents. I also notice that it fails on passing in an object that is an array or contains an array of arrays. Reordering some code we can check both of those cases at the root level:

function changeBooleanToNumber(o) {
    var toString = {}.toString,
        hasOwn = {}.hasOwnProperty,
        key,
        len;
    if (toString.call(o) === '[object Object]') {
        for (key in o) {
            if (hasOwn.call(o, key)) {
                if (toString.call(o[key]) === '[object Boolean]') {
                    o[key] = +o[key];
                } else if (toString.call(o[key]) === '[object Object]') {
                    changeBooleanToNumber(o[key]);
                } else if (toString.call(o[key]) === '[object Array]') {
                    changeBooleanToNumber(o[key]);
                }
            }
        }
    } else if (toString.call(o) === '[object Array]') {
        len = o.length;
        for (key = 0; key < len; key++) {
            if (toString.call(o[key]) === '[object Boolean]') {
                o[key] = +o[key];
            } else if (toString.call(o[key]) === '[object Object]') {
                changeBooleanToNumber(o[key]);
            } else if (toString.call(o[key]) === '[object Array]') {
                changeBooleanToNumber(o[key]);
            }
        }
    }
}

However now there is a bunch of duplicate code. This can be reduced by abstracting a function:

function changeBooleanToNumberHelper(o, key) {
    var toString = {}.toString;
    if (toString.call(o[key]) === '[object Boolean]') {
        o[key] = +o[key];
    } else if (toString.call(o[key]) === '[object Object]') {
        changeBooleanToNumber(o[key]);
    } else if (toString.call(o[key]) === '[object Array]') {
        changeBooleanToNumber(o[key]);
    }
}
function changeBooleanToNumber(o) {
    var toString = {}.toString,
        hasOwn = {}.hasOwnProperty,
        key,
        len;
    if (toString.call(o) === '[object Object]') {
        for (key in o) {
            if (hasOwn.call(o, key)) {
                changeBooleanToNumberHelper(o, key);
            }
        }
    } else if (toString.call(o) === '[object Array]') {
        len = o.length;
        for (key = 0; key < len; key++) {
            changeBooleanToNumberHelper(o, key);
        }
    }
}

Here, the if statements look very similar. It turns out we can inline this helper function:

function changeBooleanToNumber(o, key) {
    var toString = {}.toString,
        hasOwn = {}.hasOwnProperty,
        len;
    if (key) {
        if (toString.call(o[key]) === '[object Boolean]') {
            o[key] = +o[key];
            return;
        }
        o = o[key];
    }
    if (toString.call(o) === '[object Object]') {
        for (key in o) {
            if (hasOwn.call(o, key)) {
                changeBooleanToNumber(o, key);
            }
        }
    } else if (toString.call(o) === '[object Array]') {
        len = o.length;
        for (key = 0; key < len; key++) {
            changeBooleanToNumber(o, key);
        }
    }
}

Without going farther I recognize this pattern as a depth first graph traversal with a little additional functionality. I would abstract that a little and see this helper function:

function depthVisit(node, visit, key) {
    var toString = {}.toString,
        hasOwn = {}.hasOwnProperty,
        len;
    if (key) {
        if (visit(node, key) === false) {
            return;
        }
        node = node[key];
    }
    if (toString.call(node) === '[object Object]') {
        for (key in node) {
            if (hasOwn.call(node, key)) {
                depthVisit(node, visit, key);
            }
        }
    } else if (toString.call(node) === '[object Array]') {
        len = node.length;
        for (key = 0; key < len; key++) {
            depthVisit(node, visit, key);
        }
    }
}

function changeBooleanToNumber(o) {
    depthVisit(o, function (parent, key) {
        if ({}.toString.call(parent[key]) === '[object Boolean]') {
            parent[key] = +parent[key];
        }
    });
}

However I know this function has a bug: javascript can have cyclic references (causing infinite recursion). To fix that I need to either limit the traversal to a particular depth or check if I have already visited an object. Here I'll do the latter:

function depthVisit(node, visit) {
    var set = [];
    function seen(node) {
        var i = set.length - 1;
        while (set[i] !== node && i > 0) { i--; }
        set.push(node);
        return i !== -1 && set[i] === node;
    }
    function inner(node, key) {
        var toString = {}.toString,
            hasOwn = {}.hasOwnProperty,
            len;
        if (key) {
            if (visit(node, key) === false) {
                return;
            }
            node = node[key];
        }
        if (seen(node)) {
            return;
        }
        if (toString.call(node) === '[object Object]') {
            for (key in node) {
                if (hasOwn.call(node, key)) {
                    inner(node, key);
                }
            }
        } else if (toString.call(node) === '[object Array]') {
            len = node.length;
            for (key = 0; key < len; key++) {
                inner(node, key);
            }
        }
    }
    inner(node);
}

function changeBooleanToNumber2(o) {
    depthVisit(o, function (parent, key) {
        if ({}.toString.call(parent[key]) === '[object Boolean]') {
            parent[key] = +parent[key];
        }
    });
}

JsFiddle to show it works.

Interestingly this version is also about 20% faster than the original code. (see: http://jsperf.com/graph-traversal)

\$\endgroup\$
0
\$\begingroup\$

I would ask in return: Why do you want to convert booleans to numbers? First of all there must have been a reason to use booleans and not numbers in the first place and second most programming languages use the integer values 1 and 0 respectively for True and False (as I am a Python programmer I am not 100% sure but I think the concept extends to truethy and falsy in JS) and can be used like that.

The following is a copy/paste of a Python console and I also tested the same in an online JS interpreter which worked fine.

>>>> True + True
Out[0]: 2
>>>> False + True
Out[1]: 1
>>>> True - 1
Out[2]: 0
>>>> True * 3
Out[3]: 3

Therefore you don't have to convert the booleans to numbers, just use them as such!

\$\endgroup\$
  • \$\begingroup\$ its a valid comment mate.its a bit weird actually,i am posting this object back to an API that only accepts 1 and 0 for boolean values.Dont ask me why :) i didnt write the API so i wouldnt know.Thanks for your comment though \$\endgroup\$ – manraj82 Jul 12 '12 at 14:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.