How to compare objects using lodash regardless on their order - javascript

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

Related

Filter array of objects dynamically according to another array of objects

So I am making a filter functionality for React, so I have an array of objects, and based on another array that contains values to filter the array, I need to get the filtered values.
code: the array of objects to apply the filter to:
const citiesData = [
{
id: 1,
name: 'amritsar',
popu: '1200'
},
{
id: 2,
name: 'jalandhar',
popu: '1300'
},
{
id: 3,
name: 'phagwara',
popu: '1200'
},
{
id: 4,
name: 'ludhiana',
popu: '1400'
},
{
id: 5,
name: 'mumbai',
popu: '2000'
},
{
id: 6,
name: 'banglore',
popu: '2000'
},
{
id: 7,
name: 'ohter city 1',
popu: '1500'
},
{
id: 8,
name: 'ohter city 2',
popu: '1500'
},
{
id: 9,
name: 'anohter city 1',
popu: '2200'
},
{
id: 10,
name: 'anohter city 2',
popu: '2200'
},
]
code: filters array based on what I need to apply the conditions:
const filterCity = [
{
filterType: 'name',
filterValue: 'amritsar'
},
{
filterType: 'popu',
filterValue: '1200'
}
]
solutions I've tried:-
code: solution 1:
const filteredList = citiesData.filter(item => {
return filterCity.filter(fItem => item[fItem.filterType] === fItem.filterValue).length
})
code: solution 2:
const filteredList = citiesData.filter(item => {
return filterCity.reduce((acc, val) => {
if(item[val.filterType] === val.filterValue) {
acc = true
}
return acc;
}, false)
})
code: result I'm getting:
[
{ id: 1, name: 'amritsar', popu: '1200' },
{ id: 3, name: 'phagwara', popu: '1200' }
]
it's giving me two objects because according to the filters array I'm searching for the name and popu fields. but the expected result should be:
[ { id: 1, name: 'amritsar', popu: '1200' } ]
because the name and popu is similar in that but in the second object the name is not the same.
I want the code to check all the conditions and then give me the result. right now it's working on the individual filter and individual array item.
so can anyone help me on this!!
so, it should be an AND filter (combining all conditions)?
res = citiesData.filter(d =>
filterCity.every(f => d[f.filterType] === f.filterValue))
for the OR filter (any condition), replace every with some.

fillter arrays of objects

i have two arrays.
const department = [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
];
const models = [
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }],
},
{
id: '54',
name: 'model2',
departments: [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
],
},
];
i need to render accordions with department names and accordion details with matching models names. My question is how to filter those arrays to get models
We can map through the departments array, and add a models property that equals the models array, but filtered only to the ones that contain a matching department id.
const departments = [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
];
const models = [
{
id: "23",
name: "model1",
departments: [{ id: "1", name: "department1" }],
},
{
id: "54",
name: "model2",
departments: [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
],
},
];
const getDepartmentsWithModels = () => {
return departments.map((department) => {
return {
...department,
models: models.filter((model) => {
const modelDepartmentIds = model.departments.map(({ id }) => id);
return modelDepartmentIds.includes(department.id);
}),
};
});
};
console.log(getDepartmentsWithModels());
// [ { id: '1', name: 'department1', models: [ [Object], [Object] ] },
// { id: '2', name: 'department2', models: [ [Object] ] } ]```
I've built some code, which iterates over the departments. For each department it iterates the models and for each model it checks if the department is within the model departments.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }]
},
{
id: '54',
name: 'model2',
departments: [{ id: '1', name: 'department1' },{ id: '2', name: 'department2' }]
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.forEach(model => {
if (model.departments.find(modelDep => dep.id===modelDep.id)) {
console.log(` Model: ${model.name}`)
}
})
})
If you could change your data objects, then your code could be much smoother.
I've changed your data objects slightly by just reducing the departments in a model to be an array of department id's. This code iterates over the departments. For each department it filters the models and iterates over the filtered models to output them to the console. This is lesser code and provides much better performance.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: ['1']
},
{
id: '54',
name: 'model2',
departments: ['1', '2']
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.filter(model => model.departments.includes(dep.id)).forEach(model => {
console.log(` Model: ${model.name}`)
})
})
There are two solutions.
Using Array.reduce() --> returns an object where the key is department name and value is an array of the names of matching models:
let data1 = models.reduce((res, curr) => {
curr.departments.forEach(dep => {
if (!res[dep.name]) {
res[dep.name] = [curr.name]
} else {
if (!res[dep.name].includes(curr.name)) {
res[dep.name].push(curr.name);
}
}
})
return res;
}, {});
Using map and filter --> returns an array of kind:
[{department: [names of the models]},...]
let data2 = department.map(dep => {
let matchingModels = models.filter(model => {
return model.departments.filter(modDep => {
return modDep.name === dep.name;
}).length > 0;
}).map(mod => {
return mod.name;
});
return {
department: dep.name,
models: matchingModels
}
});

Getting parent node.name recursion is not working properly

I have an infinite tree:
const Data = [
{
id: '1',
name: 'hello',
children: [
{
id: '2',
name: 'world',
children: [
{
id: '3',
name: 'world',
children: [],
},
{
id: '4',
name: 'world',
children: [],
},
],
},
{
id: '5',
name: 'world',
children: [],
},
],
},
];
What I want to do is get the id and name of the path that leads to "world" and push it in to an array.
For example: the first path would be:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
]
second:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
]
And then push those arrays into another array.
So my result would look like this:
const result = [
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '4', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '5', name: 'world' },
],
];
I have a recursive function:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
visitedStack.push({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push([...visitedStack]);
}
return findPath(
input,
node.children,
visitedStack,
dataStack
);
});
};
But this is adding on all the paths it has visited, so the last array that is pushed into dataStack will look like this:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
{ id: '4', name: 'world' },
{ id: '5', name: 'world' },
]
Not sure how to fix this. Or is this an incorrect approach?
The problem is that your visitedStack keeps growing, as you are eventually pushing all nodes unto it. Be aware that all recursive executions of your function get the same visitedStack to work with. So pushing [...visitedStack] is not going to push a path, but all nodes that had been visited before, which after a while do not represent a path any more.
If we stick with your function, then just make sure you don't push on visited permanently, but create a copy of that stack with the extra node, which will remain in the deeper recursion, but will not contaminate the whole rest of the execution. This way that extra node will not be there in the other, sibling paths:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
let newStack = visitedStack.concat({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push(newStack);
}
return findPath(
input,
node.children,
newStack,
dataStack
);
});
};
Call as:
let result = [];
findPath("world", data, [], result);
console.log(result);
Alternative
I would however also address the following:
It is a bit odd that findPath does not return the result, but that the caller needs to provide the array in which the resulting paths should be collected. So I would suggest a function that returns the new array, not requiring the caller to pass that array as argument.
It is not useful to have a default value for a parameter, when other parameters following it, do not have a default value. Because, that means you anyway have to provide values for those other parameters, including the one that could have had a default value.
The paths that are returned still contain multiple references to the same objects. You do copy the objects into new objects, but as that new object sits in visitedStack, it will be reused when pushed potentially several times for deeper paths. So I would suggest making the object copies at the very last moment -- when the path is pushing on the result array.
Instead of repeatedly converting the input to lower case, do this only once.
Here is how you could write it:
function findPath(data, input="world") {
const result = [];
input = input.toLowerCase();
function recur(data, visitedStack) {
for (const node of data) {
const newStack = visitedStack.concat(node);
if (node.name.toLowerCase().includes(input)) {
result.push(newStack.map(o => ({id: o.id, name:o.name})));
}
recur(node.children, newStack);
}
}
recur(data, []);
return result;
}
const data = [{id: '1',name: 'hello',children: [{id: '2',name: 'world',children: [{id: '3',name: 'world',children: [],},{id: '4',name: 'world',children: [],},],},{id: '5',name: 'world',children: [],},],},];
const result = findPath(data);
console.log(result);

Collect flat array of deep nested data

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

Concat array from Object from Array

I'm currently trying to retrieve a list of metadata stored as an array, inside an object, inside an array. Here's a better explanatory example:
[
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
]
Now, my objective is to retrieve a list of metadata (by their name) without redundancy. I think that .map() is the key to success but I can't find how to do it in a short way, actually my code is composed 2 for and 3 if, and I feel dirty to do that.
The expected input is: ['Author', 'Creator', 'Created', 'Date']
I'm developping in Typescript, if that can help for some function.
You can use reduce() and then map() to return array of names.
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
var result = [...new Set(data.reduce(function(r, o) {
if (o.metadata) r = r.concat(o.metadata.map(e => e.name))
return r
}, []))];
console.log(result)
You could use Set for unique names.
var data = [{ name: 'test', metadata: [{ name: 'Author', value: 'foo' }, { name: 'Creator', value: 'foo' }] }, { name: 'otherTest', metadata: [{ name: 'Created', value: 'foo' }, { name: 'Date', value: 'foo' }] }, { name: 'finalTest' }],
names = new Set;
data.forEach(a => (a.metadata || []).forEach(m => names.add(m.name)));
console.log([...names]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
data
.filter(function(obj){return obj.metadata != undefined})
.map(function(obj){return obj.metadata})
.reduce(function(a,b){return a.concat(b)},[])
.map(function(obj){return obj.name})
A hand to hand Array.prototype.reduce() and Array.prototype.map() should do it as follows;
var arr = [
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
];
result = arr.reduce((p,c) => c.metadata ? p.concat(c.metadata.map(e => e.name))
: p, []);
console.log(result);

Categories

Resources