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:
Use +x
instead of Number(x)
; it is shorter and faster in all cases I've ever bothered to test.
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.
Cache Object.prototype.toString
to make code more readable.
Use toString instead of a regex test for checking for the bool type
simplify for
loop and store the upper bound in a variable instead of computing it every time
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)