首页 > 解决方案 > Deep map/transform js object with ability to alter keys and values

问题描述

I've seen lots of solutions for "deep mapping" over an object to change either the keys of the object, or the value of the object, but not both.

For example if given the following object:

const obj = {
  this: {
    is_not: {
      something: "we want to keep"
    },
    is: {
      a: {
        good_idea: 22
      },
      b: {
        bad_idea: 67
      },
      c: [{
        meh_idea: 22
      }]
    }
  }
}

Essentially I'd like to be able to transform it with the following function signature:

const newObj = deepTransform(obj, iterator)

function iterator (key, value) {
  if (key.endsWith("idea")) return { [key.replace("idea", "")]: value + 1} 
  else if (key === "is_not") return {}
  else return {[key]: value}
}

Running deepTransform would result in an object that looks like:

{
  this: {
    is: {
      a: {
        good: 23
      },
      b: {
        bad: 68
      },
      c: [{
        meh: 23
      }]
    }
  }
}

If anyone could help me create this function that would be awesome! I'd be more than happy if it used lodash functions under the hood.

Please ask if anything is unclear. Thanks!

标签: javascriptlodash

解决方案


You can use lodash's _.transform() to recursively iterate objects/arrays, and rebuild them with updated keys and values:

const { transform, toPairs, isObject } = _

const deepTransform = (obj, iterator) => transform(obj, (acc, val, key) => {
  const pair = toPairs(iterator(key, val))[0] // use the iterator and get a pair of key and value
  
  if(!pair) return // if pair is undefined, continue to next iteration
  
  const [k, v] = pair // get the update key and value from the pair
  
  // set the updated key and value, and if the value is an object iterate it as well
  acc[k] = isObject(v) ? deepTransform(v, iterator) : v 
})

const obj = {"this":{"is_not":{"something":"we want to keep"},"is":{"a":{"good_idea":22},"b":{"bad_idea":67},"c":[{"meh_idea":22}]}}}

const iterator = (key, value) => {
  if (String(key).endsWith("_idea")) return { [key.replace("_idea", "")]: value + 1} 
  
  if (key === "is_not") return {}
  
  return {[key]: value}
}

const newObj = deepTransform(obj, iterator)

console.log(newObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

Changing the iterator a to return a pair of [key, value], would make _.toPairs() redundant, and would simplify the code:

const { transform, isObject, isUndefined } = _

const deepTransform = (obj, iterator) => transform(obj, (acc, val, key) => {
  const [k, v] = iterator(key, val) // use the iterator and get a pair of key and value
  
  if(isUndefined(k)) return // skip if no updated key
  
  // set the updated key and value, and if the value is an object iterate it as well
  acc[k] = isObject(v) ? deepTransform(v, iterator) : v 
})

const obj = {"this":{"is_not":{"something":"we want to keep"},"is":{"a":{"good_idea":22},"b":{"bad_idea":67},"c":[{"meh_idea":22}]}}}

const iterator = (key, value) => {
  if (String(key).endsWith("_idea")) return [key.replace("_idea", ""), value + 1]
  
  if (key === "is_not") return []
  
  return [key, value]
}

const newObj = deepTransform(obj, iterator)

console.log(newObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>


推荐阅读