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

流过昼夜 提交于 2019-12-02 20:43:49

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';
});

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);
      }
    },
  });

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/

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);
    }
  },
});

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>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!