10

I have an object which contains an unknown number of other objects. Each (sub-)object may contain boolean values as strings and I want to change them to real boolean values. Here's an example object:

var myObj = {
  my1stLevelKey1: "true",
  my1stLevelKey2: "a normal string",
  my1stLevelKey3: {
    my2ndLevelKey1: {
      my3rdLevelKey1: {
        my4thLevelKey1: "true",
        my4thLevelKey2: "false"
      }
    },
    my2ndLevelKey2: {
      my3rdLevelKey2: "FALSE"
    }
  }
}

What I want in the end is this:

var myObj = {
  my1stLevelKey1: true,
  my1stLevelKey2: "a normal string",
  my1stLevelKey3: {
    my2ndLevelKey1: {
      my3rdLevelKey1: {
        my4thLevelKey1: true,
        my4thLevelKey2: false
      }
    },
    my2ndLevelKey2: {
      my3rdLevelKey2: false
    }
  }
}

Important is that the number sub-objects/levels is unknown. How can I do this effectively by either using classic JavaScript or Mootools?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Timo
  • 183
  • 1
  • 1
  • 8
  • you need a recursive iterator. look into json2.js to see how it traverses objects. you can also actually use a JSON revivor function. – dandavis May 08 '15 at 16:43

4 Answers4

20

Recursion is your friend

(function (obj) { // IIFE so you don't pollute your namespace
    // define things you can share to save memory
    var map = Object.create(null);
    map['true'] = true;
    map['false'] = false;
    // the recursive iterator
    function walker(obj) {
        var k,
            has = Object.prototype.hasOwnProperty.bind(obj);
        for (k in obj) if (has(k)) {
            switch (typeof obj[k]) {
                case 'object':
                    walker(obj[k]); break;
                case 'string':
                    if (obj[k].toLowerCase() in map) obj[k] = map[obj[k].toLowerCase()]
            }
        }
    }
    // set it running
    walker(obj);
}(myObj));

The obj[k].toLowerCase() is to make it case-insensitive

Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • What does `has = Object.prototype.hasOwnProperty.bind(obj);` mean? – Josiah Yoder Feb 03 '23 at 15:58
  • How would you extend this to work for arrays? – Josiah Yoder Feb 03 '23 at 15:59
  • 1
    `has` is a safe shorthand for [`obj.hasOwnProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty); by binding the object prototype version we avoid using an unexpected thing provided somewhere else in the prototype chain, i.e. the client – Paul S. Feb 06 '23 at 18:10
  • `work for arrays?` What makes you think it doesn't? What did you try? It might be worth looking for another question or opening one if there is nothing similar. – Paul S. Feb 06 '23 at 18:13
  • I don't know if arrays are 'object's in JavaScript. I'm not developing in this space, so I don't have a dev environment to check the code myself -- it's a colleague that is working on this. – Josiah Yoder Feb 07 '23 at 20:14
  • In the language of Java (not Javascript), it sounds like you are making sure that even if it is overridden by the client, you get the parent class's behaviors. I'm sure this analogy has limitations, but I can see why this is important when I think about it this way. Thank you! – Josiah Yoder Feb 07 '23 at 20:17
0

Walk each level of the object and replace boolean string values with the appropriate booleans. If you find an object, recurse in and replace again.

You can use Object.keys to grab all the members of each object, without worrying about getting inherited properties that you shouldn't.

var myObj = {
  my1stLevelKey1: "true",
  my1stLevelKey2: "a normal string",
  my1stLevelKey3: {
    my2ndLevelKey1: {
      my3rdLevelKey1: {
        my4thLevelKey1: "true",
        my4thLevelKey2: "false"
      }
    },
    my2ndLevelKey2: {
      my3rdLevelKey2: "FALSE"
    }
  }
};

function booleanizeObject(obj) {
  var keys = Object.keys(obj);
  keys.forEach(function(key) {
    var value = obj[key];
    if (typeof value === 'string') {
      var lvalue = value.toLowerCase();
      if (lvalue === 'true') {
        obj[key] = true;
      } else if (lvalue === 'false') {
        obj[key] = false;
      }
    } else if (typeof value === 'object') {
      booleanizeObject(obj[key]);
    }
  });
}

booleanizeObject(myObj);

document.getElementById('results').textContent = JSON.stringify(myObj);
<pre id="results"></pre>
ssube
  • 47,010
  • 7
  • 103
  • 140
0

JavaScript data structures elegantly can be sanitized by recursive functional reduce approaches.

var myObj = {
  my1stLevelKey1: "true",
  my1stLevelKey2: "a normal string",
  my1stLevelKey3: {
    my2ndLevelKey1: {
      my3rdLevelKey1: {
        my4thLevelKey1: "true",
        my4thLevelKey2: "false"
      }
    },
    my2ndLevelKey2: {
      my3rdLevelKey2: "FALSE"
    }
  }
};


myObj = Object.keys(myObj).reduce(function sanitizeBooleanStructureRecursively (collector, key) {
  var
    source  = collector.source,
    target  = collector.target,

    value   = source[key],

    str
  ;
  if (value && (typeof value == "object")) {

    value = Object.keys(value).reduce(sanitizeBooleanStructureRecursively, {

      source: value,
      target: {}

    }).target;

  } else if (typeof value == "string") {

    str   = value.toLowerCase();
    value = ((str == "true") && true) || ((str == "false") ? false : value)
  }
  target[key] = value;

  return collector;

}, {

  source: myObj,
  target: {}

}).target;


console.log(myObj);
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
0

Plain javascript recursion example:

function mapDeep( obj ) {
    for ( var prop in obj ) {
        if ( obj[prop] === Object(obj[prop]) ) mapDeep( obj[prop] );
        else if ( obj[prop].toLowerCase() === 'false' ) obj[prop] = false;
        else if ( obj[prop].toLowerCase() === 'true' ) obj[prop] = true;
    }                   
};

And MooTools example, by extending the Object type with custom mapDeep() function:

Object.extend( 'mapDeep', function( obj, custom ) {
    return Object.map( obj, function( value, key ) {
        if ( value === Object( value ) )
            return Object.mapDeep( value, custom );
        else
            return custom( value, key );                    
    });
});


myObj = Object.mapDeep( myObj, function( value, key ) {
    var bool = { 'true': true, 'false': false };        
    return value.toLowerCase() in bool ? bool[ value.toLowerCase() ] : value;           
}) 
Danijel
  • 12,408
  • 5
  • 38
  • 54
  • 2
    why do you use `obj[prop] === Object(obj[prop])`, why don't you use `typeof obj[prop] === 'object'` ? – vdegenne Sep 22 '17 at 14:07