将具有普通对象数组的普通对象展开为平面普通对象

时间:2016-07-08 01:28:48

标签: javascript arrays recursion combinatorics

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

让我们假装有一个对象

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。领先的“。”在key中对于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,所以如果有人将它转移到那里,我就可以了。

1 个答案:

答案 0 :(得分: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));