Collect flat array of deep nested data - javascript

I have dictionary:
var objectSchemasList = {
1: [
{
name: 'list_field1_1',
uuid: 'uuid1',
fieldObjectSchemaId: 2
},
{
name: 'list_field1_2',
uuid: 'uuid2',
fieldObjectSchemaId: null
},
],
2: [
{
name: 'list_field2_1',
uuid: 'uuid3',
fieldObjectSchemaId: null
},
{
name: 'list_field2_2',
uuid: 'uuid4',
fieldObjectSchemaId: null
},
],
3: [
{
name: 'list_field3_1',
uuid: 'uuid5',
fieldObjectSchemaId: 1
},
{
name: 'list_field3_2',
uuid: 'uuid6',
fieldObjectSchemaId: null
},
],
}
And array of related data to it:
const objectSchemaFields = [
{
name: 'field_1',
uuid: '_uuid1',
fieldObjectSchemaId: null
},
{
name: 'field_2',
uuid: '_uuid2',
fieldObjectSchemaId: null
},
{
name: 'field_3',
uuid: '_uuid3',
fieldObjectSchemaId: 1
},
];
It means that every object schema field can contain inside themselves other fields. That are linked by fieldObjectSchemaId. This mean that objectSchemaFields[2] element use objectSchemasList[objectSchemaFields[2].fieldObjectSchemaId]. That also uses objectSchemasList[2] and so on. It can be nested infinitely. I want to get flat array from this structure. Here i tried. Final array should be flat and has only path, name, uuid properties. Where path consists of concatenation of parent name and all nested child names splitted by point. For example result should be:
const result = [
{
path: 'field_1',
name: 'field_1',
uuid: '_uuid1',
},
{
path: 'field_2',
name: 'field_2',
uuid: '_uuid2',
},
{
path: 'field_3',
name: 'field_3',
uuid: '_uuid3',
},
{
path: 'field_3.list_field1_1',
name: 'list_field1_1',
uuid: 'uuid1',
},
{
path: 'field_3.list_field1_1.list_field2_1',
name: 'list_field2_1',
uuid: 'uuid3',
},
{
path: 'field_3.list_field1_1.list_field2_2',
name: 'list_field2_2',
uuid: 'uuid4',
},
{
path: 'field_3.list_field1_2',
name: 'list_field1_2',
uuid: 'uuid2',
}
]

It's not a good use case for map, because you still need to return the original object together with child objects, and you need to flatten it afterwards. Better stick with plain old array variable, or use reduce if you want to be fancy.
var output = [];
function processObject(path, obj) {
path = path.concat([obj.name]);
output.push({
path: path.join("."),
name: obj.name,
uuid: obj.uuid,
});
var schema = objectSchemasList[obj.fieldObjectSchemaId];
if (schema) {
schema.forEach(processObject.bind(null, path));
}
}
objectSchemaFields.forEach(processObject.bind(null, []));
https://jsfiddle.net/m8t54bv5/

You could reduce the arrays with a recursive call of a flattening function.
function flat(p) {
return function (r, { name, uuid, fieldObjectSchemaId }) {
var path = p + (p && '.') + name;
r.push({ path, name, uuid });
return (objectSchemasList[fieldObjectSchemaId] || []).reduce(flat(path), r);
};
}
var objectSchemasList = { 1: [{ name: 'list_field1_1', uuid: 'uuid1', fieldObjectSchemaId: 2 }, { name: 'list_field1_2', uuid: 'uuid2', fieldObjectSchemaId: null }], 2: [{ name: 'list_field2_1', uuid: 'uuid3', fieldObjectSchemaId: null }, { name: 'list_field2_2', uuid: 'uuid4', fieldObjectSchemaId: null }], 3: [{ name: 'list_field3_1', uuid: 'uuid5', fieldObjectSchemaId: 1 }, { name: 'list_field3_2', uuid: 'uuid6', fieldObjectSchemaId: null }] },
objectSchemaFields = [{ name: 'field_1', uuid: '_uuid1', fieldObjectSchemaId: null }, { name: 'field_2', uuid: '_uuid2', fieldObjectSchemaId: null }, { name: 'field_3', uuid: '_uuid3', fieldObjectSchemaId: 1 }],
result = objectSchemaFields.reduce(flat(''), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Javascript- Compare 2 object excluding certain keys

I'm creating 2 objects based on given values in my jest test and expect that they would be equal except of the property uuid which is generated indevidualy for each object.
uuid can be deeply nested multiple times. for example:
const object1 = { uuid: '5435443', name: 'xxx', branches: [{ uuid: '643643', children: [{ uuid: '65654' /* and so on */ }] }];
const object2 = { uuid: '7657657', name: 'xxx', branches: [{ uuid: '443444', children: [{ uuid: '09809' }] }];
How can I compare the objects, ignoring uuid property?
You can remove uuid first than compare them.
const object1 = { uuid: '5435443', name: 'xxx', branches: [{ uuid: '643643', children: [{ uuid: '65654' /* and so on */ }] }]};
const object2 = { uuid: '7657657', name: 'xxx', branches: [{ uuid: '443444', children: [{ uuid: '09809' }] }]};
const removeUuid = o => {
if (o) {
switch (typeof o) {
case "object":
delete o.uuid;
Object.keys(o).forEach(k => removeUuid(o[k]));
break;
case "array":
o.forEach(a => removeUuid(a));
}
}
}
removeUuid(object1);
removeUuid(object2);
expect(object1).toBe(object2);
I succeeded to achieve this with the following solution:
const object1 = { uuid: '5435443', name: 'xxx', branches: [{ uuid: '643643', children: [{ uuid: '65654' /* and so on */ }] }] };
const object2 = { uuid: '7657657', name: 'xxx', branches: [{ uuid: '443444', children: [{ uuid: '09809' }] }] };
const compareExcludeKeys = (object1, object2, excludeKeys = []) => {
if (Object.keys(object1).length !== Object.keys(object2).length) return false;
return Object.entries(object1).reduce((isEqual, [key, value]) => {
const isValueEqual = typeof value === 'object' && value !== null
? compareExcludeKeys(value, object2[key], excludeKeys)
: excludeKeys.includes(key) || object2[key] === value;
return isEqual && isValueEqual;
}, true);
};
console.log(compareExcludeKeys(object1, object2, ['uuid']));
You can use expect.any to ignore uuid attribute. Just make sure that the attributes are existing, and the uuid is a string:
describe("Compare 2 objects", () => {
it("should ...", () => {
const actual = { uuid: '5435443', name: 'xxx', branches: [{ uuid: '643643', children: [{ uuid: '65654' }] }] };
const expected = {
uuid: expect.any(String), // <--
name: 'xxx',
branches: [{ uuid: expect.any(String), children: [{ uuid: expect.any(String) }] }]
};
expect(actual).toEqual(expected);
});
});

How to convert tree having nodes stored as key-value objects to a tree structure having nodes stored as array of objects in Javascript?

I am trying to convert a tree structure having nodes stored as key-value objects to a tree structure having nodes stored as array of objects. How could I achieve it?
Example: I would like to convert this structure:
const treeX = [
{
id: '7d2a730a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category1',
nodes: {
'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1': {
name: 'subcategory2',
id: 'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: {
'69c7481d-d8c3-41da-b8fa-84d6056f6344': {
name: 'subsubcategory3',
id: '69c7481d-d8c3-41da-b8fa-84d6056f6344',
nodes: {},
},
'184e0b3d-108c-45ee-8c62-a81b73bf5b7b': {
name: 'subsubcategory4',
id: '184e0b3d-108c-45ee-8c62-a81b73bf5b7b',
nodes: {},
},
},
},
'0ec9a897-6e06-4c06-a780-b37ce8fd51f7': {
name: 'subcategory5',
id: '0ec9a897-6e06-4c06-a780-b37ce8fd51f7',
nodes: {
'9531f8d4-0d28-4a89-8d4e-ddd3ceea4939': {
name: 'subsubcategory6',
id: '9531f8d4-0d28-4a89-8d4e-ddd3ceea4939',
nodes: {},
},
'2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca': {
name: 'subsubcategory7',
id: '2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca',
nodes: {},
},
},
},
},
},
{
id: '985ab20a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category8',
nodes: {
'26b97abd-6d3f-478c-b4ea-a5fc1ad66fb1': {
name: 'subcategory9',
id: '236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: {},
},
},
},
];
to:
const treeY = [
{
id: '7d2a730a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category1',
nodes: [
{
name: 'subcategory2',
id: 'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: [
{
name: 'subsubcategory3',
id: '69c7481d-d8c3-41da-b8fa-84d6056f6344',
nodes: [],
},
{
name: 'subsubcategory4',
id: '184e0b3d-108c-45ee-8c62-a81b73bf5b7b',
nodes: [],
},
],
},
{
name: 'subcategory5',
id: '0ec9a897-6e06-4c06-a780-b37ce8fd51f7',
nodes: [
{
name: 'subsubcategory6',
id: '9531f8d4-0d28-4a89-8d4e-ddd3ceea4939',
nodes: [],
},
{
name: 'subsubcategory7',
id: '2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca',
nodes: [],
},
],
},
],
},
{
id: '985ab20a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category8',
nodes: [
{
name: 'subcategory9',
id: '236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: [],
},
],
},
];
The way how I've achieved it:
categories.forEach(category => {
category.nodes = Object.values(category.nodes);
category.nodes?.forEach(subcategory => {
subcategory.nodes = Object.values(subcategory.nodes);
subcategory.nodes?.forEach(subsubcategory => {
subsubcategory.nodes = Object.values(subsubcategory.nodes);
subsubcategory.nodes?.forEach(subsubsubcategory => {
subsubsubcategory.nodes = Object.values(subsubsubcategory.nodes)
})
})
})
});
I know that the solution is absolutely uneffective and it won't work if the tree would have more levels. Therefore, I would like to ask, how can I improve the code? Maybe using recursive function?
Looks to me all you really need is to recursively map the Object.values of the nodes property:
const treeX=[{id:"7d2a730a-53b7-4ce6-b791-446a7dc3d97e",name:"category1",nodes:{"d043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1":{name:"subcategory2",id:"d043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1",nodes:{"69c7481d-d8c3-41da-b8fa-84d6056f6344":{name:"subsubcategory3",id:"69c7481d-d8c3-41da-b8fa-84d6056f6344",nodes:{}},"184e0b3d-108c-45ee-8c62-a81b73bf5b7b":{name:"subsubcategory4",id:"184e0b3d-108c-45ee-8c62-a81b73bf5b7b",nodes:{}}}},"0ec9a897-6e06-4c06-a780-b37ce8fd51f7":{name:"subcategory5",id:"0ec9a897-6e06-4c06-a780-b37ce8fd51f7",nodes:{"9531f8d4-0d28-4a89-8d4e-ddd3ceea4939":{name:"subsubcategory6",id:"9531f8d4-0d28-4a89-8d4e-ddd3ceea4939",nodes:{}},"2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca":{name:"subsubcategory7",id:"2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca",nodes:{}}}}}},{id:"985ab20a-53b7-4ce6-b791-446a7dc3d97e",name:"category8",nodes:{"26b97abd-6d3f-478c-b4ea-a5fc1ad66fb1":{name:"subcategory9",id:"236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1",nodes:{}}}}];
const transformObj = (obj) => ({
...obj,
nodes: Object.values(obj.nodes).map(transformObj)
});
const result = treeX.map(transformObj);
console.log(result);

How to compare objects using lodash regardless on their order

I am trying to compare two objects using lodash like below. The problem is that it always returns false. I think that the issue is that the objects have different order of keys and values. I however couldn't find a solution on how to compare it regardless on the order.
How to ignore the order and compare the two objects correctly?
var obj1 = {
event: 'pageInformation',
page: { type: 'event', category: 'sportsbook' },
username: 'anonymous',
pagePath: '/',
item: { name: 'Barcelona - Leganes', id: '123' },
contest: { name: '1.Španielsko', id: 'MSK70' },
category: { name: 'Futbal', id: 'MSK3' },
teams: [
{ id: 'barcelona', name: 'Barcelona' },
{ id: 'leganes', name: 'Leganes' }
]
}
var obj2 = {
event: 'pageInformation',
page: { type: 'event', category: 'sportsbook' },
username: 'anonymous',
pagePath: '/',
category: { id: 'MSK3', name: 'Futbal' },
contest: { name: '1.Španielsko', id: 'MSK70' },
item: { id: '123', name: 'Barcelona - Leganes' },
teams: [
{ name: 'Barcelona', id: 'barcelona' },
{ name: 'Leganes', id: 'leganes' }
]
}
function compareObjects(obj1, obj2){
return _.isMatch(obj1, obj2);
}
You can use the isEqual function which does a deep equal check (regardless of key order):
_.isEqual(obj1, obj2)
See more here: https://lodash.com/docs/2.4.2#isEqual

Building a tree recursively in JavaScript

I am trying to build a tree recursively from an array of objects. I am currently using the reduce() method to iterate through the items in the array and figure out which children belong to a particular item and populating it, then recursively populating the children of those children and so on. However, I have been unable to take the last nodes(e.g persian and siamese in this case) and put them in array(see expected and current output below)
let categories = [
{ id: 'animals', parent: null },
{ id: 'mammals', parent: 'animals' },
{ id: 'cats', parent: 'mammals' },
{ id: 'dogs', parent: 'mammals' },
{ id: 'chihuahua', parent: 'dogs' },
{ id: 'labrador', parent: 'dogs' },
{ id: 'persian', parent: 'cats' },
{ id: 'siamese', parent: 'cats' }
];
const reduceTree = (categories, parent = null) =>
categories.reduce(
(tree, currentItem) => {
if(currentItem.parent == parent){
tree[currentItem.id] = reduceTree(categories, currentItem.id);
}
return tree;
},
{}
)
console.log(JSON.stringify(reduceTree(categories), null, 1));
expected output:
{
"animals": {
"mammals": {
"cats": [ // <-- an array of cat strings
"persian",
"siamese"
],
"dogs": [ // <-- an array of dog strings
"chihuahua",
"labrador"
]
}
}
}
current output:
{
"animals": {
"mammals": {
"cats": { // <-- an object with cat keys
"persian": {},
"siamese": {}
},
"dogs": { // <-- an object with dog keys
"chihuahua": {},
"labrador": {}
}
}
}
}
How should I go about solving the problem?
I put a condition to merge the result as an array if a node has no child. Try this
let categories = [
{ id: 'animals', parent: null },
{ id: 'mammals', parent: 'animals' },
{ id: 'cats', parent: 'mammals' },
{ id: 'dogs', parent: 'mammals' },
{ id: 'chihuahua', parent: 'dogs' },
{ id: 'labrador', parent: 'dogs' },
{ id: 'persian', parent: 'cats' },
{ id: 'siamese', parent: 'cats' }
];
const reduceTree = (categories, parent = null) =>
categories.reduce(
(tree, currentItem) => {
if(currentItem.parent == parent){
let val = reduceTree(categories, currentItem.id);
if( Object.keys(val).length == 0){
Object.keys(tree).length == 0 ? tree = [currentItem.id] : tree.push(currentItem.id);
}
else{
tree[currentItem.id] = val;
}
}
return tree;
},
{}
)
console.log(JSON.stringify(reduceTree(categories), null, 1));
NOTE: if your data structure changes again this parser might fail for some other scenarios.
Here is a solution without recursion:
const categories = [{ id: 'animals', parent: null },{ id: 'mammals', parent: 'animals' },{ id: 'cats', parent: 'mammals' },{ id: 'dogs', parent: 'mammals' },{ id: 'chihuahua', parent: 'dogs' },{ id: 'labrador', parent: 'dogs' },{ id: 'persian', parent: 'cats' },{ id: 'siamese', parent: 'cats' }];
// Create properties for the parents (their values are empty objects)
let res = Object.fromEntries(categories.map(({parent}) => [parent, {}]));
// Overwrite the properties for the parents of leaves to become arrays
categories.forEach(({id, parent}) => res[id] || (res[parent] = []));
// assign children to parent property, either as property of parent object or as array entry in it
categories.forEach(({id, parent}) => res[parent][res[id] ? id : res[parent].length] = res[id] || id);
// Extract the tree for the null-entry:
res = res.null;
console.log(res);

Javascript filtering nested arrays

I'm trying to filter a on a nested array inside an array of objects in an Angular app. Here's a snippet of the component code -
var teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
What I'm trying to achieve is if I search for m5 for example my result should be -
var teams = [
{ name: 'Team1', members: [] },
{ name: 'Team2', members: [{ name: 'm5' }] },
{ name: 'Team3', members: [] }
];
So I've got teams and filteredTeams properties and in my search function I'm doing -
onSearchChange(event: any): void {
let value = event.target.value;
this.filteredTeams = this.teams.map(t => {
t.members = t.members.filter(d => d.name.toLowerCase().includes(value));
return t;
})
}
Now this does work to some extent however because I'm replacing the members it's destroying the array on each call (if that makes sense). I understand why this is happening but my question is what would be the best way to achieve this filter?
you were very close, the only thing that you did wrong was mutating the source objects in teams
basically you can use spread operator to generate a new entry and then return a whole new array with new values.
const teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
const value = 'm5';
const result = teams.map(t => {
const members = t.members.filter(d => d.name.toLowerCase().includes(value));
return { ...t, members };
})
console.log(result)
Check this. Instead of hard coded m5 pass your value.
const teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
const filteredTeams = teams.map(team => ({ name: team.name, members: team.members.filter(member => member.name.includes('m5')) }));
console.log(filteredTeams);
You are mutating the original objects, but you could assing new properties to the result object for mapping instead.
var teams = [{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] }, { name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] }, { name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }],
result = teams.map(o => Object.assign(
{},
o,
{ members: o.members.filter(({ name }) => name === 'm5') }
));
console.log(result);
console.log(teams);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try to seperate your filter function first:
const filterTeamMembers = (teams, filterArr) => {
const useFilter = filterArr.map(x => x.toLowerCase());
return teams.map(team => ({
...team,
members: team.members.filter(member => useFilter.includes(member.name))
}))
};
// =========== And then:
onSearchChange(event: any): void {
let value = event.target.value;
this.filteredTeams = filterTeamMembers(this.teams, [value]);
}

Categories

Resources