Mapping and filtering a nested object - javascript

I'm trying to find all objects that contain filter with the Name of Industry and the Value of Tech. Here's my object:
const obj = [{
"Id": 1,
"Name": "Video Games",
"Labels": [{
"Name": "Industry",
"Value": "TMT"
},
{
"Name": "Analyst",
"Value": "Jen Cardage"
}
]
},
{
"Id": 2,
"Name": "Software",
"Labels": [],
},
{
"Id": 3,
"Name": "Internet",
"Labels": [{
"Name": "Industry",
"Value": "Tech"
},
{
"Name": "Analyst",
"Value": "Mike Smith"
}
]
}
]
This gets me objects with non-empty Labels, in this case, Ids 1 and 3.
const containsLabels = obj.filter(({Labels}) => Labels.length > 0);
However, I can't seem to figure out how to chain another map and/or filter onto containsLabels to map over and check against the Names and Value contained therein.
How can I get the object with Id: 3 out of containsLabels? My running code is here.

Labels is an array (as is obj), so running find() nested should do the trick:
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);
I forked your fiddle with the working version.
It is unnecessary to filter for objects where Label.length > 0, because find() will return null when running on an empty array.
If Labels is not always present, the above code would throw an error. Instead, you can use the optional chaining operator (?.), or the traditional &&:
const containsLabels = obj.filter(({ Labels }) =>
Labels?.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);
// Or...
const containsLabels = obj.filter(({ Labels }) =>
Labels && Labels.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);

You could do find directly in the first filter
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(Label => Label.Value === 'Tech')
)
console.log(containsLabels)
Runnable example
const obj = [
{
Id: 1,
Name: 'Video Games',
Labels: [
{
Name: 'Industry',
Value: 'TMT'
},
{
Name: 'Analyst',
Value: 'Jen Cardage'
}
]
},
{
Id: 2,
Name: 'Software',
Labels: []
},
{
Id: 3,
Name: 'Internet',
Labels: [
{
Name: 'Industry',
Value: 'Tech'
},
{
Name: 'Analyst',
Value: 'Mike Smith'
}
]
}
]
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(Label => Label.Value === 'Tech')
)
console.log(containsLabels)
Or just continue chaining if you want, because filter returns the new array of the same elements but filtered
const containsLabels = obj
.filter(({ Labels }) => Labels.length > 0)
.filter(({ Labels }) => Labels.find(Label => Label.Value === 'Tech'))

Related

How to filter array of objects with nested objects with specific properties?

I am trying to filter a Javascript array of objects with nested objects with specific properties. I can filter the name, slug, website, launch year without any issues. But, I can not filter the category name (category.name) which is an object within the object. Why is filtering the category name not working?
var search = "qui"; // does not work (category.name)
// var search = "Sauer"; // works (name)
var data = [{ "name": "Sauer-Metz", "slug": "ab-laborum",
"website": "https://test.com", "launch_year": 2017, "category_id": 6,
"category": { "id": 6, "name": "qui", "slug": "qui" } } ];
var results = data.filter(company => [
'name', 'launch_year', 'website', 'category.name'
].some(key => String(company[key]).toLowerCase().includes(search.toLowerCase())));
console.log(results);
One way you can go about it is to have a value extractor like the one getKey below
const getKey = (value, key) => {
return key.split('.').reduce((acc, curr) => value[curr], '');
}
var results = data.filter(company => [
'name', 'launch_year', 'website', 'category.name'
].some(key => String(getKey(company, key)).toLowerCase().includes(search.toLowerCase())));
I believe you have to do a separate condition for this specific nested property, although there might be a cleaner way I don't see right now:
var results = data.filter(
(company) =>
["name", "launch_year", "website"].some((key) =>
String(company[key]).toLowerCase().includes(search.toLowerCase())
) ||
String(company["category"]["name"])
.toLowerCase()
.includes(search.toLowerCase())
);
Dot notation doesn't work like that.
const testCase1 = 'qui';
const testCase2 = 'Sauer';
const data = [
{
name: 'Sauer-Metz',
slug: 'ab-laborum',
website: 'https://test.com',
launch_year: 2017,
category_id: 6,
category: { id: 6, name: 'qui', slug: 'qui' },
},
];
const searchResults = (data, search) => {
return data.filter((item) => {
return (
item?.category?.name.toLowerCase().includes(search.toLowerCase()) ||
['name', 'launch_year', 'website'].some((key) => `${item[key]}`.toLowerCase().includes(search.toLowerCase()))
);
});
};
console.log('**CASE 1**')
console.log(searchResults(data, testCase1));
console.log('**CASE 2**')
console.log(searchResults(data, testCase2));
To use your approach you can convert 'category.name' to ['category','name'] and then use String(company[key[0]][key[1]])... whenever key is an array.
const search = "qui"; // does not work (category.name)
//const search = "Sauer"; // works (name)
const data = [{ "name": "Sauer-Metz", "slug": "ab-laborum", "website": "https://test.com", "launch_year": 2017, "category_id": 6, "category": { "id": 6, "name": "qui", "slug": "qui" } } ];
const results = data.filter(
company => [
'name', 'launch_year', 'website', ['category','name']
].some(
key =>
Array.isArray(key) ?
String(company[key[0]][key[1]]).toLowerCase().includes(search.toLowerCase()) :
String(company[key]).toLowerCase().includes(search.toLowerCase())
)
);
console.log(results);

How Can I Make a conditional with return in a .map without get out of main function?

My objective is create this object:
{
name: string,
birthday: date
}
by this array:
const dataArray = [
{
"id": "name",
"type": "string"
},
{
"id": "birthday",
"type": "date"
}
]
So, I create a .map of the array, like this:
const inputsValues = {};
dataArray.map(item => {
if(item.type === "date"){
inputsValues[item.id] = new Date(item.value);
return; // this line is the problem
}
inputsValues[item.id] = item.value;
});
Is there a option to make a return in this function without use else?
Using map only to loop on an array, without using the returned array, is an anti pattern. The function map creates a new array by applying the given callback to all the values of an array ie dataArray. This is not your case because you want an object at the end, not an array.
Tu build your structure you should use, instead, a for of loop, a forEach or Array.reduce().
In case you just need to reduce your array to one single object, reduce is the best option:
const dataArray = [
{
"id": "name",
"type": "string"
},
{
"id": "birthday",
"type": "date"
}
];
const obj = dataArray.reduce((carry, { id, type, value = '' }) => {
carry[id] = type === 'date' ? new Date() : value;
return carry;
}, {}); // remember to initialize your carry, see reduce doc.
console.log(obj); // My reduced object { name: '', date: 2022-...}
const data = [
{id: "name", type: "string", value: "John"},
{id: "birthday", type: "date", value: "02.02.22"},
{id: "holiday", type: "date", value: "03.03.33"}
]
const inputsValues = data
// the "return" function - filters all non "date" items
.filter(item => item.type != "date")
// the map + inputsValue combination in one function
.reduce((values, item) => ({[item.id]: item.value, ...values}), {})
console.log(inputsValues)
const dataArray = [{
"id": "name",
"type": "string"
},
{
"id": "birthday",
"type": "date"
}
]
console.log(
dataArray.reduce((acc, curr) => {
acc[curr.id] = curr.type;
return acc;
}, {})
);
Is this what you're trying to achieve?
const dataArray = [
{ "id": "name", "type": "string" },
{ "id": "birthday", "type": "date" }
]
console.log(dataArray.reduce((acc, obj, i) => {
const key = obj.id;
const value = obj.type;
acc[key] = value;
return acc;
}, {}))

want to display all maximum 'levelNumber' of objects in an array

arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
my result array should have objects with max levelNumber i.e 3 in this case.
it should look like:
resultArr = [
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
note that here levelNumber can be anything..
please help me with the generic nodejs code to get duplicate max value objects
You can first find the max level of all the objects in the array and then filter the array
arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
const maxLevel = String(Math.max(...arr1.map(obj => Number(obj.levelNumber))))
const maxLevelObjects = arr1.filter(obj => obj.levelNumber === maxLevel)
console.log(maxLevelObjects);
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
];
const levelNumbers = data.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
const highestLevelItems = data.filter((item) => item.levelNumber == maxLevelNumber);
console.log(highestLevelItems);
/* output
[
{ levelNumber: '3', name: 'abc' },
{ levelNumber: '3', name: 'raks' }
]
*/
EDIT
As #nat mentioned in comment:
if I add one more object in the array, with name = 'raks & levelNumber = '4' then it should display maximum levelNumber wrt that particular name. i.e.
{ "levelNumber": "3", "name": "abc" }, { "levelNumber": "4", "name": "raks" }
To achieve this, you have to:
make a Set of names
make a separate empty array to hold final result
repeat the above process for each name and add result in the array
return complete result
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
},
{
"levelNumber": "4",
"name": "raks",
},
{
"levelNumber": "5",
"name": "raks",
}
];
// 1.
const names = new Set(data.map((item) => item.name)); // Set is used to get only unique items
// 2.
const result = []; // For normal JS
// const result: Array<{levelNumber: string, name: string}> = []; // For TS
// 3.
names.forEach((name) => {
/* minify data (filter items with only particular name) e.g. [{levelNumber: '2', name: 'abc'}, {levelNumber: '3', name: 'abc'}] */
const minifiedData = data.filter((item) => item.name === name);
/* same process, now for minified array */
const levelNumbers = minifiedData.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
minifiedData.forEach((item) => {
if (item.levelNumber == maxLevelNumber)
result.push(item); // push every matching item (item with highest level) in final result
});
});
// 4.
console.log(result);
const arr1 = [
{
levelNumber: '2',
name: 'abc',
},
{
levelNumber: '3',
name: 'abc',
},
{
levelNumber: '3',
name: 'raks',
},
];
const getHighLevelElements = (array) => {
if (array.length === 0) return null;
array.sort((elem1, elem2) => {
if (Number(elem1.levelNumber) < Number(elem2.levelNumber)) {
return 1;
}
if (Number(elem1.levelNumber) > Number(elem2.levelNumber)) {
return -1;
}
return 0;
});
return array.filter((elem) => elem.levelNumber === array[0].levelNumber);
};
const resultArr = getHighLevelElements([...arr1]);
console.log(resultArr);
I would first have a variable called highestLevel to store the highest level number found in the array of objects (will be used later while looping), loop through the whole array and checking every key levelNumber and storing that number IF highestLevel is lower than the value of the current object levelNumber.
After I've looped through the array and got the actual highestLevel number, I would loop through again and only get the objects that are equivalent to my variable highestLevel
You can just iterate one time over arr1 with Array.prototype.reduce()
Code:
const arr1 = [{levelNumber: '2',name: 'abc',},{levelNumber: '3',name: 'abc',},{levelNumber: '3',name: 'raks'}]
const result = arr1.reduce((a, c) => !a.length || +c.levelNumber === +a[0].levelNumber
? [...a, c]
: +c.levelNumber > +a[0].levelNumber
? [c]
: a,
[])
console.log(result)

JS: Filter() Array of Objects based on nested array in object?

The data
const data = [
{
id: 1,
title: "Product Red",
inventoryItem: {
inventoryLevels: {
edges: [{ node: { location: { name: "Warehouse Red" } } }],
},
},
},
{
id: 2,
title: "Product Blue",
inventoryItem: {
inventoryLevels: {
edges: [{ node: { location: { name: "Warehouse Blue" } } }],
},
},
},
];
let result = data.filter((product) => {
return product.inventoryItem.inventoryLevels.edges.forEach((inventoryLevel) => {
return inventoryLevel.node.location.name !== "Warehouse Blue";
});
});
console.log(result);
What I want to do is filter by location name. I am not sure how to construct filtering based on nested arrays.
So the result I want is if the location isn't Warehouse Blue. data should just have the object where location name is warehouse red.
You should get your work done using findIndex() instead of your forEach.
This method would search and return the index of your condition, if is not found it will return -1
let result = data.filter(product => product.inventoryItem.inventoryLevels.edges.findIndex(item => item.node.location.name !== "Warehouse Blue") !== -1 )
let result = data.filter((product) => {
return product?.inventoryItem?.inventoryLevels?.edges
.some(edge => edge?.node?.location?.name !== ('Warehouse Blue'))
});
Can use lodash too Lodash: how do I use filter when I have nested Object?

Categorisation of objects by comparing two objects in javascript

I am trying to categorise the objects by comparing two objects say data and categories
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category":["A","1a", "2a"],
"B_category":["1b", "2b"],
"C_category":["1c", "2c"],
"D_category":["1d", "2d"]
};
I want to group the data based on the category object, when there is no match the group should be others and the resultant data should be like
const resultData = [
{ group: 'Others', name: '777', count: 456 },
{ group: 'A_category', name: '1a', count: 154 },
{ group: 'B_category', name: '1b', count: 765 },
{ group: 'C_category', name: '1c', count: 7877 }
]
I used the function but not able to achieve the result
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
for (let [key, value] of Object.entries(categories)) {
value.includes(dataKey) ? resultData.push({"group": key,...data[dataKey]}) : resultData.push({"group": "Others",...data[dataKey]}) ;
break;
}
}
)
}
restructure(data,categories);
You can try this as well. Iterate over your data entries and find whether the key exists in any of the categories object data and push it into the array with found category as group or push it with Others as group as shown in the below code
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
Object.entries(data).map(([key, val])=>{
let group = Object.keys(categories).find(category=>categories[category].includes(key)) || 'Others'
resultData.push({
group,
...val
})
})
console.log(resultData)
Instead of for loop you need to use filter as let category = Object.entries(categories).filter(([key, value]) => value.includes(dataKey));.
If category.length > 0 then category is available else use Others.
Try it below.
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
let category = Object.entries(categories)
.filter(([key, value]) => value.includes(dataKey));
resultData.push({
"group": category.length > 0 ? category[0][0] : "Others",
...data[dataKey]
});
})
}
restructure(data, categories);
console.log(resultData);
That's because you're breaking out of the loop regardless of whether you found the category or not. Your for loop will only execute once then breaks immediately. If the first category object matches, it is used, if not "Others" is assigned and the loop exits without checking the rest of the categories. Only break out of the loop if the lookup is successful:
for (let [key, value] of Object.entries(categories)) {
if(value.includes(dataKey)) { // if this is the category
resultData.push({ "group": key, ...data[dataKey] }); // use it ...
return; // ... and break the loop and the current iteration of forEach. The current object is handled
}
}
resultData.push({ "group": "Others", ...data[dataKey] }); // if the return statement above is never reached, that means the category was not found, assign "Others"
BTW, you can use other array methods to shorten things out like so:
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
Then use like so:
const resultData = restructure(data, categories);
My method uses find to try to find a category key that contains the name of the object, if find fails, it returns null at which point, the || "Others" part is evaluated and "Others" will be used as the group name (Does JavaScript have "Short-circuit" evaluation?).
Demo:
const data = {"777":{"name":"777","count":456},"1a":{"name":"1a","count":154},"1b":{"name":"1b","count":765},"1c":{"name":"1c","count":7877}};
const categories = {"A_category":["A","1a","2a"],"B_category":["1b","2b"],"C_category":["1c","2c"],"D_category":["1d","2d"]};
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
const resultData = restructure(data, categories);
console.log(resultData);

Categories

Resources