Object transformation using recursive function - javascript

I've got an object that looks like this:
{
parent: {
child1: {
key: 'value'
},
child2: {
key: 'value'
},
child3: {
key: 'value'
}
}
}
I need to transform it to an object that looks like this:
{
title: 'parent',
children: [{
title: 'child1',
children: [{
title: 'key',
value: 'value'
}]
}, {
title: 'child2',
children: [{
title: 'key',
value: 'value'
}]
}, {
title: 'child3',
children: [{
title: 'key',
value: 'value'
}]
}]
}
I ended up with a following function:
function transform(obj) {
const result = {
title: '',
children: []
};
for (let key in obj) {
let child = obj[key];
result.title = key;
if (typeof(child) === 'string') {
delete result.children;
result.value = child;
} else {
result.children.push(transform(child));
}
}
return result;
}
But when I run it, it returns me the following output, which is wrong:
{
title: 'parent',
children: [{
title: 'child3',
children: [
{ title: 'key', value: 'value' },
{ title: 'key', value: 'value' },
{ title: 'key', value: 'value' }
]
}]
}
Could anyone point out what exactly is my mistake in the function, please?

I think you've chosen the wrong base case for the tree recursion. Put the leaf detection at the top of the function, not in the loop:
function transform(title, value) {
if (typeof value === 'string') {
return {title, value};
} else {
const children = [];
for (let key in obj) {
children.push(transform(key, obj[key]));
}
return {title, children};
}
}
Since you only want the single child of the root node, you'd call it as
console.log(transform('parent', data.parent));
or
console.log(transform('', data).children[0]);

Here is what you want:
const o = {
parent: {
child1: {
key: 'value'
},
child2: {
key: 'value'
},
child3: {
key: 'value'
}
}
};
const r = {};
const struct = (root, c) => {
Object.entries(root).map(([k, v]) => {
if (typeof v === 'object') {
const el = { title: k, children: [] };
c.push(el);
struct(v, el.children);
} else {
c.push({ title: k, value: v });
}
});
}
r.title = 'parent';
r.children = [];
struct(o.parent, r.children);
console.log(r);

While the recursion here is fairly simple, you have an odd requirement. What would you expect if the root node had multiple properties?
I handle this with a wrapper function which uses the recursive one to build an array of similar nodes, but then takes only the first result. This seems far from ideal, but it works:
const _transform = (obj) =>
Object .entries (obj) .map (([k, v]) => ({
title: k,
...(Object (v) === v ? {children: _transform (v)} : {value: v})
}))
const transform = (obj) => _transform (obj) [0]
const input = {parent: {child1: {key: 'value'}, child2: {key: 'value'}, child3: {key: 'value'}}}
console .log (transform (input))
.as-console-wrapper {min-height: 100% !important; top: 0}
Alternate API
This code would be simpler with the API suggested by Bergi. This is the same idea as that code but with an implementation in my style:
const transform = (title, value) =>
Object (value) === value
? {title, children: Object .entries (value) .map (([k, v]) => transform (k, v))}
: {title, value}
const input = {parent: {child1: {key: 'value'}, child2: {key: 'value'}, child3: {key: 'value'}}}
console .log (
transform ('parent', input.parent)
)
.as-console-wrapper {min-height: 100% !important; top: 0}

Related

Javascript - Remove object from nested array

I have array of objects, each object must have key and title, but children is optional, and it can be nested, i can have children inside of children many times. I want to remove some object by provided key value (for example key 677). I tried with filter but i only remove first level. Also have tried recursion, but not sure if i did it right.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const rem = '677';
const del = (el) => {
if (!el.children) {
return el.key !== rem;
} else {
if (el.key !== rem) {
del(el.children);
return el;
}
}
};
const res = data.filter((el) => {
return del(el);
});
console.log(res);
I guess your existing solution is like
const data = [
{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey);
}
So it works on the first level but not deeply.
Just change it like that will do the jobs
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey).map( e => {
return { ...e, children: removeByKey(e.children || [], removingKey)}
});
}
Little warning, children property will not be set to [] for every item not having any children.
So how it works? Well instead of keeping acceptable items as they are, we make a copy using {...e} that's equivalent to {key:e.key, title:e.title, children:e.children} in this case.
We know force to override the property children with removeByKey(e.children || [], removingKey), so we call the method recursively. Not the function works deeeply.
I would use a recursion approach with findIndex and splice. Using some will allow the code to exit without running through the entire tree.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const removeKey = (data, key) => {
// look to see if object exists
const index = data.findIndex(x => x.key === key);
if (index > -1) {
data.splice(index, 1); // remove the object
return true
} else {
// loop over the indexes of the array until we find one with the key
return data.some(x => {
if (x.children) {
return removeKey(x.children, key);
} else {
return false;
}
})
}
}
console.log(removeKey(data, '677'))
console.log(JSON.stringify(data));
You can use some simple recursion to do the trick:
const data = [
{
key: '1',
title: 'title 1',
children: [
{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}
]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(key, arr) {
// loop through all items of array
for(let i = 0; i < arr.length; i++) {
// if array item has said key, then remove it
if(arr[i].key === key) {
arr.splice(i, 1);
} else if(typeof(arr[i].children) !== "undefined") {
// if object doesn't have desired key but has children, call this function
// on the children array
removeByKey(key, arr[i].children);
}
}
}
removeByKey('098', data);
console.log(data);
This may be a little easier to understand than the other answer provided.

How to go through a complex object formatting the given required fields using a function passed by parameter?

I'm trying to create a function that goes through a complex object formatting the given fields in an array.
The function must receive the object that must be formatted, then the next parameter is an array with the attributes that must be formatted and finally the last function receives the function that will format the value of the field.
The function must return the object in it's original structure.
my code until now:
const formatFields = (obj = {}) => (fieldsToFormat = []) => (formatFunction = () => {}) => {
let newObj = { ...obj };
for (let [k, v] of Object.entries(obj)) {
if (typeof v === 'object' && v !== null) formatFields(v)(fieldsToFormat)(formatFunction);
if (fieldsToFormat.includes(k)) newObj = { ...newObj, [k]: formatFunction(v) };
else newObj = { ...newObj, [k]: v };
}
return newObj;
}
const toMoney = (num) => '$' + num;
const obj = {
totalAmount: 83.24,
quoteItems:
[ { max: '1',
code: '1',
quantity: 1,
unitPrice: 23.21,
totalPrice: 23.21,
description: 'test'
},{
max: '3',
code: '2',
quantity: 3,
unitPrice: 20.01,
totalPrice: 60.03,
description: 'test2'
} ],
};
const priceFormatAttributes = [
'unitPrice',
'totalPrice',
'totalAmount'
];
console.log(formatFields(obj)(priceFormatAttributes)(toMoney));
The nested objects are not being formatted!
I know this is a logical problem... it's being challenging and I can't move on.
I think that the recursion is wrong but I can't see why!
if anyone has an idea how to solve this in another way it is also welcome.
While I'm not sure what the expected output is I would try this:
const formatFields = (obj = {}) => (fieldsToFormat = []) => (formatFunction = () => {}) => {
let newObj = { ...obj }; // clone object to prevent changing the original object
if (Array.isArray(obj)) { // gotta deal with arrays too unless you want to change them all into objects
newObj = [ ...obj ];
}
for (let [key, value] of Object.entries(obj)) {
if (Array.isArray(newObj)) {
newObj.splice(key - 1, 1); // remove the previous it
newObj.push(formatFields(value)(fieldsToFormat)(formatFunction));
}
else if (typeof value === 'object' && value !== null) {
newObj = { ...newObj, [key]: formatFields(value)(fieldsToFormat)(formatFunction) };
}
else if (fieldsToFormat.includes(key)) {
newObj[key] = formatFunction(value)
}
else newObj = { ...newObj, [key]: value };
}
return newObj;
}
const toMoney = (num) => {
return '$' + num;
}
const obj = {
totalAmount: 83.24,
quoteItems: [
{
max: '1',
code: '1',
quantity: 1,
unitPrice: 23.21,
totalPrice: 23.21,
description: 'test',
},
{
max: '3',
code: '2',
quantity: 3,
unitPrice: 20.01,
totalPrice: 60.03,
description: 'test2',
}
],
};
const priceFormatAttributes = [
'unitPrice',
'totalPrice',
'totalAmount',
];
console.log(formatFields(obj)(priceFormatAttributes)(toMoney));
This outputs:
{
totalAmount: '$83.24',
quoteItems: [
{
max: '1',
code: '1',
quantity: 1,
unitPrice: '$23.21',
totalPrice: '$23.21',
description: 'test'
},
{
max: '3',
code: '2',
quantity: 3,
unitPrice: '$20.01',
totalPrice: '$60.03',
description: 'test2'
}
]
}

Make new object array from object in javascript [duplicate]

This question already has answers here:
Convert object to an array of objects?
(5 answers)
Closed 2 years ago.
i have object like:
let obj = {
type: 1
name: 'Test'
pr: 0
g: 1
}
and i wan't to make like this object with it:
obj = [
{
title: "type",
value: 1,
},
{
title: "name",
value: "Test",
},
{
title: "pr",
value: 0,
},
{ title: "gr", value: 1 },
];
so basically first key will be in name and second key will be value.
What i tried?
newObj = Object.keys(obj).forEach(function (key) {
obj = {
name: key,
value: key,
};
});
it's worked but it's give just first 2 value of the object and not makes object array.
so output is :
obj = {
name: type,
value: 1,
};
another entries not visible.
What i need to do ?
You can use Object.entries:
const obj = {
type: 1,
name: 'Test',
pr: 0,
g: 1
};
const res = Object.entries(obj)
.map(([title, value]) => ({title, value}));
console.log(res);
If you're not familiar with this syntax:
([title, value]) => ({title, value})
It could be rewritten as:
function (x) { return { title: x[0], value: x[1] }; }
But in the above example, I used a destructuring assignment to create 2 variables title and value from the provided key/value pair.
And {title, value} is just a shorthand syntax for { title: title, value: value }.
You can use Object.keys(...) and use map to get your desired result like below.
Object.keys(obj).map(x => ({ title: x, value: obj[x] }));
const obj = {
type: 1,
name: 'Test',
pr: 0,
g: 1
};
const res = Object.keys(obj).map(x => ({ title: x, value: obj[x] }));
console.log(res);
Also you can do this by reduce method with Object.keys(), as an alternative:
let obj = {
type: 1,
name: 'Test',
pr: 0,
g: 1,
}
const result = Object.keys(obj).reduce((acc, rec) => {
return [...acc, {
title: rec,
value: obj[rec]
}]
}, [])
console.log(result);

How to turn dot notation string into an object in Javascript

I'm trying to turn a dot notation string into an object, for example given
[{
key: 'app.team.instance',
value: 'some value1'
}, {
key: 'app.team.instance.obj',
value: 'some value'
}, {
key: 'app.team.app.some',
value: 'some value'
}, {
key: 'app.service.awesome.more',
value: 'more values'
}]
I would like to turn it an object like to get
{
"team": {
"instance": "some value1",
"server": {
"obj": "some value"
},
"app": {
"some": "some value"
}
},
"service": {
"awesome": {
"more": "more values"
}
}
}
This's what I have tried using the following function. Looking at my function what have I missed or should improve?
function createObjFromRows(skip, key, value, obj) {
const ARRAY_KEYS = key.split('.');
const ARRAY_LENGTH = ARRAY_KEYS.length;
let i = skip ? 1 : 0;
for (; i < ARRAY_LENGTH; i++) {
if (i < (ARRAY_LENGTH - 1)) {
if (!obj.hasOwnProperty(ARRAY_KEYS[i])) { obj[ARRAY_KEYS[i]] = {}; }
} else {
obj[ARRAY_KEYS[i - 1]][ARRAY_KEYS[i]] = value;
}
}
}
This's what I get currently.
{
team: {
instance: 'some value1'
},
server: {
obj: 'some value'
},
app: {
some: 'some value'
},
service: {},
awesome: {
more: 'more values'
}
}
You can use array.prototype.reduce :
var datas = [ {key: 'app.team.instance', value: 'some value1'}, {key: 'app.team.server.obj', value: 'some value'}, {key: 'app.team.app.some',value: 'some value'}, {key: 'app.service.awesome.more', value: 'more values'}];
var res = datas.reduce((m, o) => {
var keys = o.key.split('.');
var cur = m;
keys.forEach((key, i) => {
if (i < keys.length - 1) {
cur[key] = cur[key] || {};
cur = cur[key];
} else {
cur[key] = o.value;
}
});
return m;
}, {});
console.log(res);
You could split the given key strings and save the last key for the assignment of the value after iterating the keys to the nested property.
var data = [{ key: 'app.team.instance', value: 'some value1' }, { key: 'app.team.server.obj', value: 'some value' }, { key: 'app.team.app.some', value: 'some value' }, { key: 'app.service.awesome.more', value: 'more values' }],
result = data.reduce(function (r, o) {
var path = o.key.split('.'),
last = path.pop();
path.reduce(function (p, k) {
return p[k] = p[k] || {};
}, r)[last] = o.value;
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Convert object with one property into parent property

I have converted javascript object from xml, this is example of object:
{
name: 'current name',
attr1: 'attribute1',
attr2: 'attribute2',
address: {
name: 'name1',
value: {
value: '12'
},
attr3: {
name: 'no name',
attr4: {
attr4: 'attribute4'
}
}
},
price: {
price: '500'
},
in_house: {
in_house: '2'
}
}
how I can convert into this:
{
name: 'current name',
attr1: 'attr1',
address:{
name: 'name1',
value: '12',
attr3: {
name: 'no name',
attr4: 'attribute3'
}
}
attr2: 'attr2',
price: 500,
in_house: 2
}
need convert all unusefull object into property, example
{
price :
price: '500'
}
into
{ price: '500'}
You could use an iterative, recursive approach for the keys and their values.
function moveUp(object, last) {
var keys = Object.keys(object);
if (keys.length === 1 && keys[0] in last) {
last[keys[0]] = object[keys[0]];
if (last[keys[0]] !== null && typeof last[keys[0]] === 'object') {
moveUp(last[keys[0]], last);
}
return;
}
keys.forEach(function (k) {
if (object[k] !== null && typeof object[k] === 'object') {
moveUp(object[k], object)
}
});
}
var object = { name: 'current name', attr1: 'attribute1', attr2: 'attribute2', address: { name: 'name1', value: { value: '12' }, attr3: { name: 'no name', attr4: { attr4: 'attribute4' } } }, price: { price: '500' }, in_house: { in_house: '2' }, test: { test: { test: { banane: 42 } } } };
moveUp(object);
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here is a recursive function that will iterate over the root object and pass over every node in it to see if the current node has an immediate child of the same name.
const obj = { name: 'current name', attr1: 'attribute1', attr2: 'attribute2',
address: { name: 'name1', value: { value: '12' }, attr3: { name: 'no name', attr4: { attr4: 'attribute4' }}}, price: { price: '500' }, in_house: { in_house: '2' }}
// helper function to check if a value is an object
const isObject = thing => (
typeof thing !== 'undefined' &&
typeof thing.constructor &&
thing.constructor === Object
)
const mutateUselessProperties = (root) => {
// we need to recursively go through the root object and return it's result
// after removing properties so we create an inner function for recursion
const go = (obj) => {
// if it's just a value return it
if (!isObject(obj)){
return obj
}
// it's an object so we loop over the keys
for (let key in obj) {
// check if it's an object with a child of the same key
if (isObject(obj[key]) && obj[key][key]) {
// reassign the property to it's child of the same name
obj[key] = obj[key][key]
}
// check if it's still an object after possible reassignment
if (isObject(obj[key])) {
// it's an object so recrusively go through the child properties
obj[key] = go(obj[key])
}
// may as well check if we are dealing with an array at the same time
if (Array.isArray(obj[key])) {
obj[key] = obj[key].map(go)
}
}
// return the current iteration
return obj
}
// run the recursive iteration
go(root)
// return the root object that has been mutated
return root
}
console.log(mutateUselessProperties(obj))
If the nested single properties all have the same name with the parent property then the following should work;
var obj = {
name: 'current name',
attr1: 'attribute1',
attr2: 'attribute2',
address: {
name: 'name1',
value: {
value: '12'
},
attr3: {
name: 'no name',
attr4: {
attr4: 'attribute4'
}
}
},
price: {
price: '500'
},
in_house: {
in_house: '2'
}
};
for (var prop in obj) typeof obj[prop] === "object" && Object.keys(obj[prop]).length === 1 && (obj[prop] = obj[prop][prop]);
console.log(obj);

Categories

Resources