【问题标题】:How to split object into nested object? (Recursive way)如何将对象拆分为嵌套对象? (递归方式)
【发布时间】:2020-04-08 15:18:20
【问题描述】:

我有一个包含下划线(_)变量名称的数据集。如下:

const data = {
   m_name: 'my name',
   m_address: 'my address',
   p_1_category: 'cat 1',
   p_1_name: 'name 1',
   p_2_category: 'cat 2',
   p_2_name: 'name 2'
}

我想将它们拆分成嵌套的对象/数组,下面是我想要的结果。

{
 m: {
     name: "my name",
     address: "my address"
 },
 p: {
    "1": {category: 'cat 1', name: 'name 1'}, 
    "2": {category: 'cat 2', name: 'name 2'}
 } 
}

如何编写递归方法来实现它而不是硬编码? 也许它应该允许处理更深的嵌套对象,例如 "p_2_one_two_category: 'value'" 到 p:{2:{one:{two:category:'value'}}}

var data ={
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}

var o ={};
Object.keys(data).forEach(key => {
  var splited =  key.split(/_(.+)/);
  if (!o[splited[0]]) {
    o[splited[0]] = {};
  }
  var splited1 = splited[1].split(/_(.+)/);
  if (splited1.length < 3) {
    o[splited[0]][splited[1]] = data[key];
  } else {
    if (!o[splited[0]][splited1[0]]){ 
      o[splited[0]][splited1[0]] = {};
    }
    o[splited[0]][splited1[0]][splited1[1]] = data[key];
  }
});
console.log(o);

【问题讨论】:

  • 您的代码和 Nenad 答案中的代码都会生成与您的示例输出略有不同的内容。 (m 是一个普通对象,而不是单一属性对象的数组。)这些结果似乎比您的示例输出更合乎逻辑。那是你真正想要的吗?
  • @ScottSauyet ops,我输入了错误的结果,我已经更新了它,但我的 sn-p 与他所做的相同。是的,这就是我想要的......谢谢

标签: javascript arrays object recursion


【解决方案1】:

您可以使用reduce 方法,该方法将仅使用对象创建类似的嵌套结构。

var data = {
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}


const result = Object
  .entries(data)
  .reduce((a, [k, v]) => {
    k.split('_').reduce((r, e, i, arr) => {
      return r[e] || (r[e] = arr[i + 1] ? {} : v)
    }, a)

    return a
  }, {})

console.log(result)

【讨论】:

    【解决方案2】:

    我不知道该输出格式是您真正想要的还是您能够完成的最好的格式。一种替代方法是生成如下内容:

    {
      m: {name: "my name", address: "my address"},
      p: [
        {category: "cat 1", name: "name 1"},
        {category: "cat 2", name: "name 2"}
      ]
    }
    

    这与您的代码输出之间有一个主要区别。 p 现在是一个简单的对象数组,而不是 1- 和 2-indexed 对象。这很可能对您没有帮助,但这是一个有趣的选择。与您提供的示例输出还有第二个区别。您的原始代码和 Nenad 的答案都返回 m: {name: "my name", address: "my address"} 而不是请求的 m: [{name: "my name"}, {address: "my address"}]。这对我来说似乎更合乎逻辑,而且我也是这样做的。

    这里有一些代码可以做到这一点:

    // Utility functions
    
    const isInt = Number.isInteger
    
    const path = (ps = [], obj = {}) =>
      ps .reduce ((o, p) => (o || {}) [p], obj)
    
    const assoc = (prop, val, obj) => 
      isInt (prop) && Array .isArray (obj)
        ? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
        : {...obj, [prop]: val}
    
    const assocPath = ([p = undefined, ...ps], val, obj) => 
      p == undefined
        ? obj
        : ps.length == 0
          ? assoc(p, val, obj)
          : assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)
    
    // Main function
    
    const hydrate = (flat) => 
      Object .entries (flat) 
        .map (([k, v]) => [k .split ('_'), v])
        .map (([k, v]) => [k .map (p => isNaN (p) ? p : p - 1), v])
        .reduce ((a, [k, v]) => assocPath (k, v, a), {})
    
    // Test data
    
    const data = {m_name: 'my name', m_address: 'my address', p_1_category: 'cat 1', p_1_name: 'name 1', p_2_category: 'cat 2', p_2_name: 'name 2' }
    
    // Demo
    
    console .log (
      hydrate (data)
    )
    .as-console-wrapper {min-height: 100% !important; top: 0}

    这段代码的灵感来自Ramda(我是其中的作者)。实用函数 pathassocassocPath 具有与 Ramda 相似的 API,但这些是独特的实现(借用自 another answer。)由于这些是内置在 Ramda 中的,因此只需要 hydrate 函数如果你的项目使用 Ramda。

    这与 Nenad 的(优秀的!)答案之间的最大区别在于,我们的对象水合考虑了字符串键(假定用于普通对象)和数字键(假定用于数组)之间的差异。但是,由于这些是从我们的初始字符串 (p_1_category) 中拆分出来的,如果您有时可能希望将它们作为对象,这可能会导致歧义。

    我还使用了一个有点难看的技巧,可能不会扩展到其他数值:我从数字中减去 1,以便 p_1_category 中的 1 映射到第零个索引。如果您的输入数据看起来像 p_0_category ... p_1_category 而不是 p_1_category ... p_2_category 我们可以跳过这个。

    无论如何,这很有可能与您的基本要求相反,但它可能对其他人有用。

    【讨论】:

    • 我喜欢在这里看到不同的方法。也许path 可以是ps.reduce((o = {}, p) =&gt; o[p], obj),也就是ps.reduce(prop, obj)
    • 抱歉,我提供了错误的信息。正确的应该基于我的sn-p。顺便说一句,这很酷。将来可能会有用。谢谢。
    • @Thankyou:虽然path 的那个版本可能适用于这种特定情况,但它失去了上述版本的空安全性。同意答案的多样性;似乎有趣的问题可以带来许多有趣的选择。
    【解决方案3】:

    无需排序

    您帖子中建议的输出不遵循模式。有些项目分组为数组,而另一些则分组为对象。由于类数组对象的行为类似于数组,因此我们将只使用对象。

    此答案中的输出与 Nenad 的相同,但此程序不需要事先对对象的键进行排序 -

    const nest = (keys = [], value) =>
      keys.reduceRight((r, k) => ({ [k]: r }), value)
    
    const result =
      Object
        .entries(data)
        .map(([ k, v ]) => nest(k.split("_"),  v))
        .reduce(merge, {})
    
    console.log(result)
    

    输出 -

    {
      m: {
        name: "my name",
        address: "my address"
      },
      p: {
        1: {
          category: "cat 1",
          name: "name 1"
        },
        2: {
          category: "cat 2",
          name: "name 2",
          contact: "1234567"
        }
      },
      k: {
        id: 111,
        name: "abc"
      }
    }
    

    合并

    我正在借用我在另一个答案中写的通用merge。重用泛型函数的好处很多,我不会在这里重复它们。如果您想了解更多信息,请阅读original post -

    const isObject = x =>
      Object (x) === x
    
    const mut = (o = {}, [ k, v ]) =>
      (o[k] = v, o)
    
    const merge = (left = {}, right = {}) =>
      Object.entries (right)
        .map
          ( ([ k, v ]) =>
              isObject(v) && isObject(left[k])
                ? [ k, merge (left[k], v) ]
                : [ k, v ]
          )
        .reduce(mut, left)
    

    浅合并按预期工作 -

    const x =
      [ 1, 2, 3, 4, 5 ]
    
    const y =
      [  ,  ,  ,  ,  , 6 ]
    
    const z =
      [ 0, 0, 0 ]
    
    console.log(merge(x, y))
    // [ 1, 2, 3, 4, 5, 6 ]
    
    console.log(merge(y, z))
    // [ 0, 0, 0, <2 empty items>, 6 ]
    
    console.log(merge(x, z))
    // [ 0, 0, 0, 4, 5, 6 ]
    

    还有深度合并 -

    const x =
      { a: [ { b: 1 }, { c: 1 } ] }
    
    const y =
      { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }
    
    console.log(merge (x, y))
    // { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }
    

    展开下面的 sn-p 以在您自己的浏览器中查看我们的结果 -

    const isObject = x =>
      Object(x) === x
    
    const mut = (o = {}, [ k, v ]) =>
      (o[k] = v, o)
    
    const merge = (left = {}, right = {}) =>
      Object
        .entries(right)
        .map
          ( ([ k, v ]) =>
              isObject(v) && isObject(left[k])
                ? [ k, merge(left[k], v) ]
                : [ k, v ]
          )
        .reduce(mut, left)
    
    const nest = (keys = [], value) =>
      keys.reduceRight((r, k) => ({ [k]: r }), value)
    
    const data =
      {m_name:'my name',m_address:'my address',p_1_category:'cat 1',p_1_name:'name 1',p_2_category:'cat 2',p_2_name:'name 2',p_2_contact:'1234567',k_id:111,k_name:'abc'}
    
    const result =
      Object
        .entries(data)
        .map(([ k, v ]) => nest(k.split("_"),  v))
        .reduce(merge, {})
      
    console.log(JSON.stringify(result, null, 2))

    【讨论】:

      【解决方案4】:

      在对象上使用forEach 循环。
      根据分隔符拆分键并遍历数组
      直到最后一个键,创建空对象并在指针/运行器中维护当前对象。
      在最后一个键上,只需添加值即可。

      const unflatten = (data, sep = "_") => {
        const result = {};
        Object.entries(data).forEach(([keys_str, value]) => {
          let runner = result;
          keys_str.split(sep).forEach((key, i, arr) => {
            if (i === arr.length - 1) {
              runner[key] = value;
            } else if (!runner[key]) {
              runner[key] = {};
            }
            runner = runner[key];
          });
        });
        return result;
      };
      
      const data ={
        m_name: 'my name',
        m_address: 'my address',
        p_1_category: 'cat 1',
        p_1_name: 'name 1',
        p_2_category: 'cat 2',
        p_2_name: 'name 2',
        p_2_contact: '1234567',
        k_id: 111,
        k_name: 'abc'
      }
      
      console.log(unflatten(data));

      【讨论】:

        猜你喜欢
        • 2012-02-12
        • 1970-01-01
        • 2021-05-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-27
        • 2019-09-15
        相关资源
        最近更新 更多