Recreate an array of nested objects recursively - javascript

I have an array of nested objects created by a user. I need to recursively check the array for if checked is set to true and recreate the nested array of objects as shown below.
With my original attempt I was able to recursively get through the nested array. However, every time it recursed I would overwrite my previous information.
$scope.newPropertyTemplate = [];
function checkProps (item, props){
debugger
var obj = {};
var arr = [];
for(var i in props){
if(props[i].checked === true){
obj.property = item;
arr.push(props[i].name);
obj.subProperties = arr;
}
if(props[i].properties){
checkProps(props[i].name, props[i].properties);
}
}
return obj;
}
for(var i = 0; i < $scope.propertyTemplate.length; i++){
if($scope.propertyTemplate[i].properties !== null){
$scope.newPropertyTemplate.push(checkProps($scope.propertyTemplate[i].name, $scope.propertyTemplate[i].properties));
// $scope.newPropertyTemplate.push( newItem.property = $scope.propertyTemplate[i].name );
}
else {
$scope.newPropertyTemplate.push($scope.propertyTemplate[i].name);
}
}
Here's an example of the nested array:
[
{name: "allocation",
properties:[
{
name: "oid",
checked: true,
properties: null,
type: "integer"
},
{
name: "person",
checked: false,
properties: [
{
name: "Expires",
checked: true,
properties: null,
type: "timestamp"
},
{
name: "State/Province",
checked: true,
properties: null,
type: "string"
}
]
],
type: "allocation"
]
Here's an example of the converted array of objects:
[
{property: "allocation",
subProperties: [
"oid",
{property: "person",
subProperties: [
"Expires",
"State/Province"
]
}
]
}
]

This should work. The returned obj from your recursive checkProps function was not being stored.
var data = [{
name: "allocation",
properties: [{
name: "oid",
checked: true,
properties: null,
type: "integer"
}, {
name: "person",
checked: false,
properties: [{
name: "Expires",
checked: true,
properties: null,
type: "timestamp"
}, {
name: "State/Province",
checked: true,
properties: null,
type: "string"
}]
},
],
type: "allocation"
}];
function process(paths) {
var arr = [];
data.forEach(function(template) {
var resultObj = {};
recurse(template, resultObj);
arr.push(resultObj);
});
console.log(arr);
}
function recurse(template, obj) {
obj.property = template.name;
if (template.properties == null)
return;
obj.subProperties = [];
template.properties.forEach(function(prop) {
var res = {};
if (prop.properties != null)
recurse(prop, res);
else if (prop.checked)
res = prop.name;
if (res)
obj.subProperties.push(res);
});;
}
process(data);

Related

Having issues with deleting object from an array where match is found

I have the follow function that will delete object from array. It also returns the array tree without the items that was deleted. My issues is that it works when my objToFindBy is null deleting everything where {group: null} is found however it error with promise rejection if I set objToFindBy {group: 'some string'}
This code should delete all occurrences where the objToFindBy is a match, example {group: null} will find everywhere will the group is null and delete all object and then return the full tree without the objects that was deleted
findAndDeleteAll(tree, 'items', {group: null}) // work and delete all where match. then returns the tree without deleted objects
findAndDeleteAll(tree, 'items', {group: 'd575c91f-4765-4073-a948-5e305116610c'}) // promise rejection
const tree ={
"type": "app",
"info": "Custom Layout",
"items": [
{
"id": "d575c91f-4765-4073-a948-5e305116610c",
"title": "Fc",
"group": null
},
{
"id": "890d5a1e-3f03-42cd-a695-64a17b6b9bea",
"title": null,
"group": null
},
{
"id": "cbe00537-0bb8-4837-8019-de48cb04edd6",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c",
},
{
"id": "b8751c32-2121-4907-a229-95e3e49bcb39",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c"
}
],
"Children": []
}
var findAndDeleteAll = function findAndDeleteAll(tree, childrenKey, objToFindBy) {
var treeModified = false;
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
Object.keys(tree).forEach(function (key) {
return delete tree[key];
});
return tree;
}
function innerFunc(tree, childrenKey, objToFindBy) {
if (tree[childrenKey]) {
var _loop = function _loop(index) {
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[childrenKey][index][key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
tree[childrenKey].splice(index, 1);
treeModified = true;
}
if (tree[childrenKey][index].hasOwnProperty(childrenKey)) {
innerFunc(tree[childrenKey][index], childrenKey, objToFindBy);
}
};
for (var index = tree[childrenKey].length - 1; index >= 0; index--) {
_loop(index);
}
}
}
innerFunc(tree, childrenKey, objToFindBy);
return treeModified ? tree : false;
};
how about shorter solution?
const findAndDeleteAll = (tree, childrenKey, nestedKey, nestedValue) => {
return{...tree, [childrenKey]: tree[childrenKey].filter((row) => {
return row[nestedKey] !== nestedValue;
})}
}
const a = findAndDeleteAll(tree, 'items', 'group', null) // work and delete all where match. then returns the tree without deleted objects
const b = findAndDeleteAll(tree, 'items', 'group', 'd575c91f-4765-4073-a948-5e305116610c') // promise rejection
console.warn(a);
console.warn(b);
Your function would be so much simper, reusable, better, if you send not the redundant tree - but instead deleteFrom(tree.items, "group", null);. think about it.
const deleteFrom = (arr, pr, val) => arr.filter(ob => ob[pr] !== val);
const tree = {
type: "app",
info: "Custom Layout",
items: [
{ id: "10c", title: "Fc", group: null },
{ id: "bea", title: null, group: null },
{ id: "dd6", title: null, group: "10c" },
{ id: "b39", title: null, group: "10c" },
],
Children: []
};
const items = deleteFrom(tree.items, "group", null);
console.log(items); // Only the filtered items array
const newTree = {...tree, items};
console.log(newTree); // Your brand new tree!

Add/Update a property in a deeply nested array of objects based on a given key and value

I've got a deeply nested array that looks like this:
const elements = [
{
type: "section",
id: "basic-section",
title: "Basic information",
children: [
{
type: "select",
label: "Entity type",
id: "entity-type",
options: [
{ value: "person", label: "Person" },
{ value: "company", label: "Company" },
{ value: "organisation", label: "Organisation" },
],
},
{
type: "group",
conditions: [
{ type: "isEqual", variable: "entity-type", value: ["person"] },
],
children: [
{ type: "text", label: "First name", id: "entity.firstName" },
{ type: "text", label: "Last name", id: "entity.lastName" },
{ type: "number", label: "Age", id: "entity.age" },
{
type: "select",
label: "Gender",
id: "entity.gender",
defaultValue: "female",
options: [
{ value: "male", label: "Male" },
{ value: "female", label: "Female" },
],
},
],
},
{
type: "group",
conditions: [
{
type: "isEqual",
variable: "entity-type",
value: ["company", "organisation"],
},
],
children: [
{ type: "text", label: "Name", id: "entity.name" },
{ type: "text", label: "Address", id: "entity.address" },
],
},
],
},
];
I'm trying to add and update a property based on a given key and value.
Example 1: Add an option to the options list of entity-type
Example 2: Update the defaultValue of entity.gender to male
My current steps to accomplish this actions are:
1) Find the element based on the id key and id value
const element = findObject(elements, 'id', 'entity-type');
function findObject(object, key, value) {
if(object.hasOwnProperty(key) && object[key] === value) {
return object;
}
for(let i = 0; i < Object.keys(object).length; i++){
if(typeof object[Object.keys(object)[i]] == "object") {
const o = findObject(object[Object.keys(object)[i]], key, value);
if(o !== null) return o;
}
}
return null;
}
2) Create new option
const newOption = { value: 'government', label: 'Government' };
3) Add the new option to the found element
const updatedElement = Object.assign({}, element, { options: [...element.options, newOption] });
4) Replace the old element with the updatedElement
const newElementsList = // Stuck
5) Update the state with the updatedElementsList
setElementsList(newElementsList);
I don't see how I can replace the original element with the updated one based on the key and value.
Can someone help me out?
This is not recommended, but you can keep track of parent. Once you find the element, update the parent data with update value. But you loose immutability.
A better approach would be found and update the same time.
const elements = [{"type":"section","id":"basic-section","title":"Basic information","children":[{"type":"select","label":"Entity type","id":"entity-type","options":[{"value":"person","label":"Person"},{"value":"company","label":"Company"},{"value":"organisation","label":"Organisation"}]},{"type":"group","conditions":[{"type":"isEqual","variable":"entity-type","value":["person"]}],"children":[{"type":"text","label":"First name","id":"entity.firstName"},{"type":"text","label":"Last name","id":"entity.lastName"},{"type":"number","label":"Age","id":"entity.age"},{"type":"select","label":"Gender","id":"entity.gender","defaultValue":"female","options":[{"value":"male","label":"Male"},{"value":"female","label":"Female"}]}]},{"type":"group","conditions":[{"type":"isEqual","variable":"entity-type","value":["company","organisation"]}],"children":[{"type":"text","label":"Name","id":"entity.name"},{"type":"text","label":"Address","id":"entity.address"}]}]}];
// console.log("%j", elements);
function findObject(element, key, value, { parent = null, index = -1 }) {
if (element.hasOwnProperty(key) && element[key] === value) {
return { element: element, parent, index };
}
let keys = Object.keys(element);
for (let i = 0; i < keys.length; i++) {
if (typeof element[keys[i]] == "object") {
const o = findObject(element[Object.keys(element)[i]], key, value, {
parent: element,
index: i,
});
if (o !== null) return o;
}
}
return { element: null };
}
const { element, parent, index } = findObject(
elements,
"id",
"entity-type",
{}
);
const newOption = { value: "government", label: "Government" };
const updatedElement = Object.assign({}, element, {
options: [...element.options, newOption],
});
if (parent && index !== -1) parent[index] = updatedElement;
console.log(JSON.stringify(elements, null, 4));

Deep merging nested arrays

I have to merge 2 arrays with key value as follows:
array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}]
with
array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
to produce something like
merged = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"},{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
I've been trying this out for quite some time and can't get a solution that meets the scenario. Most of the solutions just do blind merging, not based on the id value. Tried using lodash mergeWith but didn't get the output needed. A Ramda solution is also acceptable.
Thanks,
This links could be helpful to you merge two arrays.
In this code snippet, i have tried to find the common objects between set1 and set2,if there are any i'm finding the unique properties and changing their content and also non existant properties in object2 and pushing it to object1
Check the following snippet.
var arr1 = [{
id: "123",
data: [{
id: "234",
data: "hello"
}, {
id: "345",
data: "there"
}, {
id: "xyz",
data: "yo"
}]
}, {
id: "456",
data: [{
id: "34",
data: "test"
}, {
id: "45",
data: "test2"
}, {
id: "yz",
data: "test3"
}]
}, {
id: "789",
data: [{
id: "23",
data: "aaa"
}, {
id: "34",
data: "bbb"
}, {
id: "xy",
data: "ccc"
}]
}]
var arr2 = [{
id: "456",
data: [{
id: "45",
data: "changed"
}, {
id: "yz",
data: "data"
}]
}, {
id: "789",
data: [{
id: "456",
data: "appended data"
}]
}, {
id: "890",
data: [{
id: "456",
data: "new data"
}]
}]
var arr3 = [];
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
// arr1[i].data.concat(arr2[j].data);
var set1 = pushproperties(arr1[i].data, arr2[j].data);
arr1[i].data = set1;
arr3.push(arr1[i]);
break;
}
if (!shared) {
arr3.push(arr1[i]);
arr3.push(arr2[j]);
}
}
function pushproperties(set1, set2) {
var filtered = false;
set2.forEach(function(item) {
filtered = set1.every(function(element) {
return element.id != item.id;
});
if (filtered) {
set1.push(item);
}
});
set1.forEach(function(item) {
set2.forEach(function(element) {
if (item.id == element.id) {
item.data = element.data;
}
});
});
return set1;
}
console.log(arr3);
Hope this helps
This a function the merges 2 arrays recursively using Array.prototype.reduce(). If it encounters items with the same id, and they have a data prop, which is an array, it merges them using the logic. If data is not an array, it's overridden by the last item instead.
function mergeArraysDeep(arr1, arr2) {
var unique = arr1.concat(arr2).reduce(function(hash, item) {
var current = hash[item.id];
if(!current) {
hash[item.id] = item;
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, {});
return Object.keys(unique).map(function(key) {
return unique[key];
});
}
var array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
var array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
var result = mergeArraysDeep(array1, array2)
console.log(result);
ES6 version that uses Map, Map.prototype.values(), and array spread:
const mergeArraysDeep = (arr1, arr2) => {
return [...arr1.concat(arr2).reduce((hash, item) => {
const current = hash.get(item.id);
if(!current) {
hash.set(item.id, item);
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, new Map()).values()];
}
const array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
const array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
const result = mergeArraysDeep(array1, array2)
console.log(result);
Finally this is what worked for me. Thanks to #Geeky for showing the way:
function mergeArrays(arr1, arr2) {
var arr3, arrIdx = [];
if (!arr1 || arr1.length ==0) return arr2
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
joined = _.mergeWith({},arr1[i],arr2[j], function (a,b) {
if (_.isArray(a)) return b.concat(a)})
arr3.push(joined);
break;
}
if (!shared) {
arr3.push(arr1[i]);
}
}
for (var k in arr2) {
if (arrIdx[k] !=k) arr3.push(arr2[k])
}
return arr3
}

Merge array of objects with underscore

I have array of objects like this. And they have duplicated property 'contactName' values
[
{
categoryId:1
categoryName:"Default"
contactId:141
contactName:"Anonymous"
name:"Mobile"
value:"+4417087654"
},
{
categoryId:1
categoryName:"Default"
contactId:325
contactName:"Anonymous"
name:"Email"
value:"test2#gmail.com"
},
{
categoryId:1
categoryName:"Default"
contactId:333
contactName:"Anonymous"
name:"Email"
value:"ivdtest#test.com"
}
]
I want to merge them in one object by the name of property 'contactName'
To something like this
[
{
categoryId: 1,
categoryName: "Default",
contactId: 141,
contactName: "Anonymous",
names: {
1: "Mobile",
2: "Email",
3: "Email"
},
values: {
1: '+2234324',
2: "ivdtest#test.com",
3: "test2#gmail.com"
}
}
];
Edit: How can I group objects also by categoryName ?
var grouped = _.groupBy(input, 'contactName');
var output = _.map(grouped, function(entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
names: _.indexBy(_.pluck(entries, 'name'), function(val, index) { return index +1; }),
values: _.indexBy(_.pluck(entries, 'value'), function(val, index) { return index +1; })
}
);
});
https://jsfiddle.net/f1x4tscu/3/
Another variant with array inside the object
var grouped = _.groupBy(this.contacts, 'contactName');
var output = _.map(grouped, function (entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
addresses: _.map(entries, function (m) {
return {
name: m.name,
value: m.value
}
}),
}
);
});

Loop through array of objects and return object keys

My code is as follows :
let filters = [
{name: "MAKE", values:
[
{
Volkswagen: {active: true, make: "Volkswagen"},
Skoda: {active: true, make: "Skoda"}
}
]
}
]
function getFilterValues(){
return filters.filter(f => {
if(f.name == "MAKE"){
return f.values.filter(i => {
Object.keys(i).map(key => {
return key;
});
});
}
});
}
var div = document.getElementById('output');
div.innerHTML = getFilterValues();
I want to loop through filters to get the object keys.
Thus the result that I want is, in this case Volkswagen, Skoda. But my function getFilterValues doesn't return what I want.
Here is jsfiddle.
Any advice?
The main problem is with the filter function. You want map since with filter you return true/false whether the element should be included in the resulting code. Check out this diff: https://www.diffchecker.com/CX6hOoxo
This works: https://jsfiddle.net/o93Lm0rc/101/
let filters = [
{name: "MAKE", values:
[
{
Volkswagen: {active: true, make: "Volkswagen"},
Skoda: {active: true, make: "Skoda"}
}
]
}
]
function getFilterValues(){
return filters.map(f => {
if(f.name == "MAKE"){
return f.values.map(i => {
return Object.keys(i).map(key => {
return key;
});
});
}
});
}
var div = document.getElementById('output');
div.innerHTML = getFilterValues();
You can use Object.keys() to get the list of keys.
var filters = [{
name: "MAKE",
values: [{
Volkswagen: {
active: true,
make: "Volkswagen"
},
Skoda: {
active: true,
make: "Skoda"
}
}]
}];
for (i = 0; i < filters.length; i++) {
if (typeof filters[i].values == "object") {
console.log(Object.keys(filters[i].values[0]));
}
}

Categories

Resources