How to split object into nested object? (Recursive way)

僤鯓⒐⒋嵵緔 提交于 2021-02-04 22:18:12

问题


I have a data set containing underscore(_) variable name. Such as below:

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'
}

I want to split them into nested object/array, Below is the result I want.

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

How can I write a recursive method to achive it instead of hardcoded? Maybe it should allow to handle deeper nested object such as "p_2_one_two_category: 'value'" into 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);

回答1:


You could use reduce method that will create similar nested structure with just objects.

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:


I don't know if that output format was what you were really looking for or simply the best you were able to accomplish. One alternative would be to generate something like this:

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

There is one major difference between this and your code's output. p is now a plain array of objects rather than a 1- and 2-indexed object. It's quite possible that this is not helpful to you, but it's an interesting alternative. There is also a second difference from the sample output you supplied. Both your original code and the answer from Nenad return m: {name: "my name", address: "my address"} instead of the requested m: [{name: "my name"}, {address: "my address"}]. This seems much more logical to me, and I've also done it this way.

Here is some code that would do this:

// 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}

This code is inspired by Ramda (of which I'm an author). The utility functions path, assoc, and assocPath have similar APIs to Ramda's, but these are unique implementations (borrowed from another answer.) Since these are built into Ramda, only the hydrate function would be necessary if your project used Ramda.

The big difference between this and Nenad's (excellent!) answer is that our object hydration takes into account the difference between string keys, which are assumed to be for plain objects, and numeric ones, which are assumed to be for arrays. However, since these are split out of our initial string (p_1_category), this could lead to ambiguity if you might sometimes want those to be objects.

I also use a trick that is a bit ugly and maybe wouldn't scale to other numeric values: I subtract 1 from the number so that the 1 in p_1_category maps to the zeroth index. If your input data looked like p_0_category ... p_1_category instead of p_1_category ... p_2_category we could skip this.

In any case, there is a real chance that this is contrary to your underlying requirements, but it might be useful to others.




回答3:


no sorting required

The proposed output in your post does not follow a pattern. Some items group to arrays while others group to objects. Since array-like objects behave like arrays, we'll just use objects.

The output in this answer is the same as Nenad's but this program does not require that the object's keys are sorted beforehand -

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)

Output -

{
  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

I'm borrowing a generic merge that I wrote in another answer. The advantages to reusing generic functions are numerous and I won't reiterate them here. Read the original post if you'd like to know more -

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)

Shallow merges work as expected -

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 ]

And deep merges too -

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 } ] }

Expand the snippet below to see our result in your own browser -

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:


Use the forEach loop on object.
Split the key based on seperator and traverse on the array
Until the last key, create empty object and maintain the current object in pointer/runner.
On the last key, Just add the value.

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));


来源:https://stackoverflow.com/questions/61104150/how-to-split-object-into-nested-object-recursive-way

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!