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);
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With