【问题标题】:Compare objects recursively and put duplicate key values into array递归比较对象并将重复的键值放入数组
【发布时间】:2018-12-10 17:08:32
【问题描述】:

我需要迭代三个对象以检查重复项以最终得到一个对象。如果有重复项,我想将它们放入一个数组中,以便用户可以选择最佳值,以便它们最终都以字符串或整数(或带有字符串/整数的对象)的形式结束。它们可能看起来像这样:

const a = {
  first_name: 'Tom',
  height: {
    feet: 5,
    inches: 0
  }
}
const b = {
  first_name: 'Thomas',
  last_name: 'Walsh',
  email: 'tomwalsh@domain.com',
  height: {
    feet: 6,
    inches: 0
  }
}
const c = {
  email: 'tomwalsh@sample.edu'
}

结果如下所示:

const result = {
  first_name: ['Tom', 'Thomas'],
  last_name: 'Walsh',
  email: ['tomwalsh@domain.com', 'tomwalsh@sample.edu'],
  height: {
    feet: [5, 6],
    inches: 0
  }
}

我不知道 abc 是否是密钥的权威,所以我必须假设一组未知的键/值对,但它肯定很小(不超过 20 对,只有 2-3 的深度会超过 3 级,并且每个级别最多有 4-5 个键/值对。

我可以创建一个新对象,递归地迭代这三个对象中的每一个,然后转储值或创建一个值数组,但这似乎效率低下。有什么建议吗?

如果需要,我在项目中有 lodash。谢谢!

【问题讨论】:

    标签: javascript ecmascript-6 lodash


    【解决方案1】:

    由于某些对象中可能缺少嵌套键,您可以通过 lodash 的_.mergeWith() 合并它们,并将重复项收集到数组中:

    const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
    const b = {"first_name":"Thomas","last_name":"Walsh","email":"tomwalsh@domain.com","height":{"feet":6,"inches":0}}
    const c = {"email":"tomwalsh@sample.edu"}
    
    const shouldCollect = (s) => _.negate(_.overSome([
      _.isUndefined,
      _.isObject,
      _.partial(_.eq, s)
    ]))
    
    
    const mergeDupes = (...args) => _.mergeWith({}, ...args, (o, s) => {
      if(_.isArray(o)) return _.uniq([...o, s]);
      if(shouldCollect(s)(o)) return [o, s];
    })
    
    const result = mergeDupes(a, b, c)
    
    console.log(result);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

    如果你想删除不重复的属性,你可以通过递归_.transform()来清理对象:

    const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
    const b = {"first_name":"Thomas","last_name":"Walsh","email":"tomwalsh@domain.com","height":{"feet":6,"inches":0}}
    const c = {"email":"tomwalsh@sample.edu"}
    
    const shouldCollect = (s) => _.negate(_.overSome([
      _.isUndefined,
      _.isObject,
      _.partial(_.eq, s)
    ]))
    
    const omitNonDuplicates = obj =>
      _.transform(obj, (a, v, k) => {
        if (_.isArray(v)) a[k] = v;
        else if (_.isObject(v)) {
          const clean = omitNonDuplicates(v);
          if(!_.isEmpty(clean)) a[k] = clean;
          return;
        }
      });
      
    const mergeDupes = (...args) => omitNonDuplicates(_.mergeWith({}, ...args, (o, s) => {
      if(_.isArray(o)) return _.uniq([...o, s]);
      if(shouldCollect(s)(o)) return [o, s];
    }))
    
    const result = mergeDupes(a, b, c)
    
    console.log(result);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

    【讨论】:

    • 我必须接受这个作为最佳答案。它不仅解决了我的需求,还让我大吃一惊。谢谢!
    【解决方案2】:

    您将需要一个递归函数来深入研究对象属性:

    function mergeObjects(objs) {
        objs = objs.filter(obj => obj !== undefined);
        // return value or array when at least one of the values is a primitive
        if (objs.some(obj => Object(obj) !== obj)) { 
            objs = Array.from(new Set(objs)); // Get unique values
            return objs.length === 1 ? objs[0] : objs;
        }
        // Get all the keys
        const keys = Array.from(new Set([].concat(...objs.map(obj => Object.keys(obj))))); 
        return Object.assign({}, ...keys.map(key => 
            ({ [key]: mergeObjects(objs.map(obj => obj[key])) })));
    }
    
    // Demo:
    const a = {first_name: 'Tom', height: {feet: 5,inches: 0}};
    const b = {first_name: 'Thomas',last_name: 'Walsh',email: 'tomwalsh@domain.com', height: {feet: 6,inches: 0}};
    const c = {email: 'tomwalsh@sample.edu'};
    
    const result = mergeObjects([a,b, c]);
    console.log(result);

    【讨论】:

      【解决方案3】:

      我认为没有函数可以做到这一点,你必须迭代它们的属性。

      const a = { first_name: 'Tom', height: {feet: 5, inches: 0 } };
      const b = { first_name: 'Thomas', last_name: 'Walsh', email:'tomwalsh@domain.com',  height: { feet: 6, inches: 0} };
      const c = { email: 'tomwalsh@sample.edu' };
      
      let newObj = {};
      
      [a, b, c].forEach(obj => {
          Object.entries(obj).forEach(([key, value]) => {
              if (newObj.hasOwnProperty(key)) { // if the property exist
                  if (Array.isArray(newObj[key])) {
                      newObj[key].push(value);
                  } else {
                      let temporalVal = newObj[key];
                      newObj[key] = [temporalVal, value];
                  }
              } else { // if the property does not exist, create it.
                  newObj[key] = value;
              }
          })
      });
      
      console.log(newObj)

      【讨论】:

        【解决方案4】:

        您可以通过检查值来采用递归方法,并将嵌套对象用于内部值。

        const
            merge = (a, b) => {
                Object.entries(b).forEach(([k, v]) => {
                    if (v && typeof v === 'object') {
                        return merge(a[k] = a[k] || {}, v);
                    }
                    if (!(k in a)) {
                        return a[k] = v;
                    }
                    if ([].concat(a[k]).includes(v)) {
                        return;
                    }
                    a[k] = [].concat(a[k], v); 
                });
                return a;
            },
            a = { first_name: 'Tom', height: { feet: 5, inches: 0 } },
            b = { first_name: 'Thomas', last_name: 'Walsh', email: 'tomwalsh@domain.com', height: { feet: 6, inches: 0 } },
            c = { email: 'tomwalsh@sample.edu', height: { feet: 15 } },
            result = [a, b, c].reduce(merge, {});
        
        console.log(result);
        .as-console-wrapper { max-height: 100% !important; top: 0; }

        【讨论】:

          【解决方案5】:

          这是通过Array.reduceArray.forEach 提供的简洁递归解决方案:

          const a = { first_name: 'Tom', height: { feet: 5, inches: 0 } },
                b = { first_name: 'Thomas', last_name: 'Walsh', email: 'tomwalsh@domain.com', height: { feet: 6, inches: 0 } },
                c = { email: 'tomwalsh@sample.edu'},
                d = { first_name: 'Tomy', height: { feet: 15, inches: 10 } }
          
          const mergeKeys = (a,b) => {
            if(a != undefined) {
              if(a === b) return a
              if(typeof b == 'object') return merge([a,b])      
              else return [...(Array.isArray(a) ? a : [a]), b]
            } else return b
          }
          
          const merge = arr => arr.reduce((r,c) => 
            (Object.entries(c).forEach(([k,v]) => r[k] = mergeKeys(r[k],c[k])), r) ,{})
          
          console.log(merge([a,b,c,d]))

          【讨论】:

            【解决方案6】:

            Lodash 怎么样 + Deepdash:

            let merged = _.cloneDeep(objects.shift()); // clone to keep source untouched
            objects.forEach((obj) => {
              _.eachDeep(obj, (value, key, parent, ctx) => {
                if (_.isObject(value)) return;
                let exists = _.get(merged, ctx.path);
                if (exists == undefined) {
                  exists = value;
                } else {
                  exists = _.uniq([].concat(exists, value));
                  if (exists.length == 1) exists = exists[0];
                }
                _.set(merged, ctx.path, exists);
              });
            });
            

            完整的test 适合您的情况

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-03-15
              • 2016-11-10
              • 1970-01-01
              • 1970-01-01
              • 2018-02-06
              • 2019-01-07
              • 1970-01-01
              • 2021-09-19
              相关资源
              最近更新 更多