How to deeply map object keys with JavaScript (lodash)?

23,624

Solution 1

Here's how you can do that in lodash:

_.mixin({
    'deepMapKeys': function (obj, fn) {

        var x = {};

        _.forOwn(obj, function(v, k) {
            if(_.isPlainObject(v))
                v = _.deepMapKeys(v, fn);
            x[fn(v, k)] = v;
        });

        return x;
    }
});

and here's a more abstract mixin, that recursively applies any given mapper:

_.mixin({
    deep: function (obj, mapper) {
        return mapper(_.mapValues(obj, function (v) {
            return _.isPlainObject(v) ? _.deep(v, mapper) : v;
        }));
    },
});

Usage (returns the same as above):

obj = _.deep(obj, function(x) {
    return _.mapKeys(x, function (val, key) {
        return key + '_hi';
    });
});

Another option, with more elegant syntax:

_.mixin({
    deeply: function (map) {
        return function(obj, fn) {
            return map(_.mapValues(obj, function (v) {
                return _.isPlainObject(v) ? _.deeply(map)(v, fn) : v;
            }), fn);
        }
    },
});


obj = _.deeply(_.mapKeys)(obj, function (val, key) {
    return key + '_hi';
});

Solution 2

In extention of georg's answer, here's what I'm using. This extended mixin adds the ability to map arrays of objects within the object too, a simple but important change.

_.mixin({
    deeply: function (map) {
      return function (obj, fn) {
        return map(_.mapValues(obj, function (v) {
          return _.isPlainObject(v) ? _.deeply(map)(v, fn) : _.isArray(v) ? v.map(function(x) {
            return _.deeply(map)(x, fn);
          }) : v;
        }), fn);
      }
    },
  });

Solution 3

EDIT: This will map the objects values, not its keys. I misunderstood the question.


function deepMap (obj, cb) {
    var out = {};

    Object.keys(obj).forEach(function (k) {
      var val;

      if (obj[k] !== null && typeof obj[k] === 'object') {
        val = deepMap(obj[k], cb);
      } else {
        val = cb(obj[k], k);
      }

      out[k] = val;
    });

  return out;
}

And use it as

var lol = deepMap(test, function (v, k) {
    return v + '_hi';
});

It will recursively iterate over an objects own enumerable properties and call a CB passing it the current value and key. The values returned will replace the existing value for the new object. It doesn't mutate the original object.

See fiddle: https://jsfiddle.net/1veppve1/

Solution 4

Not a best one as per performance efficiency, but If you want to skip recursion part from your side and want to keep the code clean and simple then you can stringify it to json and use JSON.parse with additional parameter (callback) and there you can transform the keys with lodash.

JSON.parse(JSON.stringify(obj), (k, v) => _.isObject(v) ? _.mapKeys(v, (_v, _k) => _k + '_hi') : v)

Here is an working example:

let obj = { a: 2, b: { c: 2, d: { a: 3 } } };
let mappedObj = JSON.parse(JSON.stringify(obj), (k, v) => _.isObject(v) ? _.mapKeys(v, (_v, _k) => _k + '_hi') : v)
console.log(mappedObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

Solution 5

I've add a small improvement from Chris Jones's answer.

Following code fix some unintended results of Array elements.

_.mixin({
  deeply: function (map) {
    var deeplyArray = function (obj, fn) {
      return obj.map(function(x) {
        return _.isPlainObject(x) ? _.deeply(map)(x, fn) : x;
      })
    }

    return function (obj, fn) {
      if (_.isArray(obj)) {
        return deeplyArray(obj, fn);
      }

      return map(_.mapValues(obj, function (v) {
        return _.isPlainObject(v) ? _.deeply(map)(v, fn) : _.isArray(v) ? 
          deeplyArray(v, fn) : v;
      }), fn);
    }
  },
});
Share:
23,624
Tejas Manohar
Author by

Tejas Manohar

Updated on July 26, 2022

Comments

  • Tejas Manohar
    Tejas Manohar almost 2 years

    https://lodash.com/docs#mapKeys

    Is it possible to map an Object's keys deeply using Lodash? If not, is there another library providing this functionality (if grouped with other deep iteration and manipulation functionality, even better!)? Else, how would one go about implementing this? The main struggle I see is in identifying pure key/value objects that are safely, deeply iterable. It's easy to throw out Arrays, but it's important to note that the function shouldn't try to deeply iterate on other objects like a regex, for example.

    Intended result-

    var obj = { a: 2, b: { c: 2, d: { a: 3 } } };
    _.deepMapKeys(obj, function (val, key) {
      return key + '_hi';
    });
    // => { a_hi: 2, b_hi: { c_hi: 2, d_hi: { a_hi: 3 } } }