【问题标题】:Unfold a plain-object with array of plain-objects into a flat plain-object将具有普通对象数组的普通对象展开为平面普通对象
【发布时间】:2016-07-08 01:28:47
【问题描述】:

更新:我以错误的方式描述了问题,并完全重写了描述,以及有效但丑陋和有限的代码。


假设有一个对象

const input = {
  a: 1,
  b: '2',
  c: {
    d: true,
    e: '4'
  },
  f: [{
    g: 5,
    h: {
      i: '6'
    }
  }, {
    g: 7,
    h: {
      i: '8'
    }
  }]
}

我正在寻找的是所有可能的嵌套数组排列的集合,对象的键被展平并用“.”连接,例如

[{
  a: 1,
  b: '2',
  'c.d': true,
  'c.e': '4',
  'f.g': 5,
  'f.h.i': '6'
}, {
  a: 1,
  b: '2',
  'c.d': true,
  'c.e': '4',
  'f.g': 7,
  'f.h.i': '8'
}]

请注意,没有任何键具有非原始值,例如,'f.h' 会指向一个对象。

所以,我首先要做的是收集所有键,并人为地将# 符号添加到指向数组项的每个键,所以# 的意思是“该数组中的每个索引”:

function columns(data, prefix = '') {
  if (_.isArray(data)) {
    return columns(_.first(data), `${prefix}.#`);
  } else if (_.isObject(data)) {
    return _.filter(_.flatMap(_.keys(data), key => {
      return _.concat(
        !_.isObject(_.result(data, key)) ? `${prefix}.${key}` : null,
        columns(data[key], `${prefix}.${key}`)
      );
    }));
  } else {
    return null;
  }
}

console.log(columns(input)); // -> [".a", ".b", ".c.d", ".c.e", ".f.#.g", ".f.#.h.i"]

现在,我使用 lodash。前导“。” in keys 对 lodash 来说不是问题,所以我就让它保持原样。使用 lodash,我将对象压缩成一个带有奇怪键的单级对象:

function flattenKeys(original, keys) {
  return _.mapValues(_.groupBy(_.map(keys, key => ({
    key,
    value: _.result(original, key)
  })), 'key'), e => _.result(e, '0.value'));
}

console.log(flattenKeys(input, columns(input))) // -> {".a":1,".b":"2",".c.d":true,".c.e":"4"}

现在我(以一种非常错误的方式)运行原始对象的每个类似数组的属性并生成一个对象数组,将.f.#.h.i 之类的键设置为第一个元素的.f.0.h.i 值等:

function unfold(original, keys, iterables) {
  if (!_.isArray(iterables)) {
    return unfold(original, keys, _.uniq(_.map(_.filter(keys, key => /#/i.test(key)), key => _.replace(key, /\.\#.*/, ''))));
  } else if (_.isEmpty(iterables)) {
    return [];
  } else {
    const first = _.first(iterables);
    const rest = _.tail(iterables);
    const values = _.result(original, first);
    const flatKeys = _.mapKeys(_.filter(keys, key => _.includes(key, first)));
    const updated = _.map(values, (v, i) => ({
      ...flattenKeys(original, keys),
      ..._.mapValues(flatKeys, k => _.result(original, _.replace(k, /\#/, i)))
    }));

    return _.concat(updated, unfold(original, keys, rest));
  }
}

console.log(unfold(input, columns(input))) // -> [{".a":1,".b":"2",".c.d":true,".c.e":"4",".f.#.g":5,".f.#.h.i":"6"},{".a":1,".b":"2",".c.d":true,".c.e":"4",".f.#.g":7,".f.#.h.i":"8"}]

所以最后,我只需要清理密钥,事实上,在我的情况下,这不是必需的。

问题是,除了代码的丑陋之外,我怎样才能使它与原始对象中可能的多个类似数组的属性一起工作?


现在,我明白了,这个问题更适合 CodeReview StackExchange,所以如果有人把它转移到那里,我可以接受。

【问题讨论】:

  • 是只有两层还是任意多?
  • 这些数组中是否总是只有一个元素?如果不是,那意味着什么?如果是的话,你为什么首先使用这些数组?
  • 刚刚发现一个数组可能包含多个元素,但永远不会为空。我有工作代码,但是伙计,这有多讨厌。不过,我会分享它。
  • 更新后的示例与之前的示例完全不同,现在每个对象都包含所有扁平键。而且完全不清楚您是如何得出'f.g': 7 和'f.h.i': '8' 的值的。请修复示例;最好包括多个。
  • 当然,已更新。我差一点就删除这个问题了????但是为了历史而持有它,并且相信有人可能会在完全相同的设置下遇到相同的组合问题。

标签: javascript arrays recursion combinatorics


【解决方案1】:

根据您更新的结构,以下递归函数可以解决问题:

function unfold(input) {
  function flatten(obj) {
    var result = {},
        f,
        key,
        keyf;

    for(key in obj) {
      if(obj[key] instanceof Array) {
        obj[key].forEach(function(k) {
          f = flatten(k);
          for(keyf in f) {
            result[key+'.'+keyf] = f[keyf];
          }
          output.push(JSON.parse(JSON.stringify(result))); //poor man's clone object
        });
      } else if(obj[key] instanceof Object) {
        f = flatten(obj[key]);
        for(keyf in f) {
          result[key+'.'+keyf] = f[keyf];
        }
      } else {
        result[key] = obj[key];
      }
    }
    return result;
  } //flatten

  var output = [];
  flatten(input);
  return output;
} //unfold

片段:

function unfold(input) {
  function flatten(obj) {
    var result = {},
        f,
        key,
        keyf;

    for(key in obj) {
      if(obj[key] instanceof Array) {
        obj[key].forEach(function(k) {
          f = flatten(k);
          for(keyf in f) {
            result[key+'.'+keyf] = f[keyf];
          }
          output.push(JSON.parse(JSON.stringify(result))); //poor man's clone object
        });
      } else if(obj[key] instanceof Object) {
        f = flatten(obj[key]);
        for(keyf in f) {
          result[key+'.'+keyf] = f[keyf];
        }
      } else {
        result[key] = obj[key];
      }
    }
    return result;
  } //flatten
  
  var output = [];
  flatten(input);
  return output;
} //unfold

const input = {
  a: 1,
  b: '2',
  c: {
    d: true,
    e: '4'
  },
  f: [{
    g: 5,
    h: {
      i: '6'
    }
  }, {
    g: 7,
    h: {
      i: '8'
    }
  }]
};

document.body.innerHTML+= '<pre>' + JSON.stringify(unfold(input), null, 2) + '</pre>';

我会留下我原来的答案,它适用于你的原始结构:

var o = {a: [{b: 1, c: 2}], d: [{e: 4, f: 5}]},
    keys = Object.keys(o),
    result = [];

keys.forEach(function(i, idx1) {
  keys.forEach(function(j, idx2) {
    if(idx2 > idx1) { //avoid duplicates
      for(var k in o[i][0]) {
      	for(var l in o[j][0]) {
          result.push({
            [i + '.' + k]: o[i][0][k],
            [j + '.' + l]: o[j][0][l]
          });
        }
      }
    }
  });
});

console.log(JSON.stringify(result));

【讨论】:

  • 我可以在这段代码中看到至少两个我自己不会想出的技巧。太酷了,非常感谢!
  • 当然,这是一个有趣的挑战。
  • 如果外部对象不完全具有两个属性,这似乎会崩溃。如果是这样,你应该硬编码……
  • @Bergi,你说得对,现在问题已经更新为更复杂的结构,我的代码根本不起作用。但是,我会留下它,因为 OP 说他在其中找到了一些他可以使用的技巧。
猜你喜欢
  • 1970-01-01
  • 2016-08-30
  • 1970-01-01
  • 2017-06-06
  • 2016-09-14
  • 2020-03-14
  • 1970-01-01
  • 2018-01-03
  • 2011-01-16
相关资源
最近更新 更多