How to get the values from a nested array? - javascript

I have an array in Angular called testUsers with the following sample data:
this.testUsers = [
{email: '1#fake.com', name: 'A Smith'}
{email: '2#fake.com', name: 'B Johnson'}
{email: '3#fake.com', name: 'C Dobbs', colours:
['green', 'blue', red']
}
{email: '4#fake.com', name: 'D Mew', colours:
['black', 'blue']
}
]
What I need is to get the values inside the nested 'colours' array in a new array.
Best I can get is to end up with a value as [Array(1)] which then contains data. But I need the values, not the values as an array.
How do I fix this function?
this.newArray = this.testUsers.map(value => {
return value.colours
});

You could take a flat array as result with a default value for not given property.
const
testUsers = [{ email: '1#fake.com', name: 'A Smith' }, { email: '2#fake.com', name: 'B Johnson' }, { email: '3#fake.com', name: 'C Dobbs', colours: ['green', 'blue', 'red'] }, { email: '4#fake.com', name: 'D Mew', colours: ['black', 'blue'] }],
colours = testUsers.flatMap(({ colours = [] }) => colours);
console.log(colours);

const val1 = this.testUsers.map((value) => value.colours) // [undefined, undefined, ['green', 'blue', red'], ['black', 'blue']]
const val2 = val1.filter((val) => val !== undefined) // [['green', 'blue', red'], ['black', 'blue']]
const output = val2.flatMap(val => val) // ['green', 'blue', red', 'black', 'blue']

Related

How to return keys that have empty values from an array of objects as a string in JavaScript?

I have an array of objects where some keys return an empty value. When an empty value appears I'd like to return that key as a string.
For example this array:
'products': [{
'name': 'Test product',
'id': '',
'price': '',
'brand': 'AwesomeBrand',
'colour': 'Gray',
'quantity': 1
},
{
'name': '',
'id': '123',
'price': '',
'brand': 'AwesomeBrand',
'colour': 'Black',
'quantity': 1
}]
In the example above I want to return the following:
'id, price, name'
I've tried the following that kind of does the job. However, it still returns as an array. Then I JSON.strigify it but that returns an ugly string. I tried replacing/RegEx cleaning it however, the program I'm using uses a Sandboxed Version of JavaScript so some features are not accessible.
I'm struggling to grasp the concept of accessing keys and values in an array of objects. So that is part of my problem.
var productArray = ecomValues || [];
var emptyArray = [];
var output = productArray.reduce(function (previousValue, currentValue, currentIndex) {
var keys = Object.keys(currentValue).filter(function (key) {
return !currentValue[key];/ });
if (keys.length) {
previousValue[currentIndex] = keys;
}
return previousValue;
}, {});
var emptyValues = output;
emptyArray.push(emptyValues);
Anyone that can help me with my issue?
We can use reduce() to create an array with all the keys that are empty
To find the empty keys we can use this for an example
Then, we remove all duplicate keys
And join() the array to a string
const data = {'products': [{'name': 'Test product', 'id': '', 'price': '', 'brand': 'AwesomeBrand', 'colour': 'Gray', 'quantity': 1 }, {'name': '', 'id': '123', 'price': '', 'brand': 'AwesomeBrand', 'colour': 'Black', 'quantity': 1 }]};
const emptyKeys = data.products.reduce((prev, cur) =>
[ ...prev, ...Object.keys(cur).filter((key) => !cur[key]) ], [])
.filter((v, i, a) => a.indexOf(v) === i)
.join(', ');
console.log(emptyKeys);
id, price, name
Iterate products with Array.flatMap(), filter out all keys with non-empty values, and then make the array unique by using a Set, and spreading back to an array:
const products = [{ name: 'Test product', id: '', price: '', brand: 'AwesomeBrand', colour: 'Gray', quantity: 1 }, { name: '', id: '123', price: '', brand: 'AwesomeBrand', colour: 'Black', quantity: 1 }]
const result = [...new Set(products.flatMap(
obj => Object.keys(obj)
.filter(key => obj[key] === '')
))]
console.log(result);
You could iterate the array and get the keys and add the keys if a falsy value is found.
const
products = [{ name: 'Test product', id: '', price: '', brand: 'AwesomeBrand', colour: 'Gray', quantity: 1 }, { name: '', id: '123', price: '', brand: 'AwesomeBrand', colour: 'Black', quantity: 1 }],
empty = Array.from(products.reduce((s, o) => {
Object.keys(o).forEach(k => {
if (o[k] === '') s.add(k);
});
return s;
}, new Set));
console.log(empty.join(', '));
A solution without Set.
const
products = [{ name: 'Test product', id: '', price: '', brand: 'AwesomeBrand', colour: 'Gray', quantity: 1 }, { name: '', id: '123', price: '', brand: 'AwesomeBrand', colour: 'Black', quantity: 1 }],
empty = Array.from(products.reduce((r, o) => {
Object.keys(o).forEach(k => {
if (o[k] === '' && !r.includes(k)) r.push(k);
});
return r;
}, []));
console.log(empty.join(', '));
You can simply achieve that by iterating the array using Array.forEach() along with Object.keys() method.
Demo :
const products = [{
'name': 'Test product',
'id': '',
'price': '',
'brand': 'AwesomeBrand',
'colour': 'Gray',
'quantity': 1
}, {
'name': '',
'id': '123',
'price': '',
'brand': 'AwesomeBrand',
'colour': 'Black',
'quantity': 1
}];
const res = [];
products.forEach((obj) => {
Object.keys(obj).forEach((objKey) => {
!obj[objKey] ? res.push(objKey) : ''
})
})
// Remove duplicate elements from an array
console.log([...new Set(res)]);

Merge two objects by articlenumber and add it to a new key

I have two objects and I want merge it together by articlenumber and add new fields in a detail key. I tried it with lodash but I got a flat object.
1st Object:
products = {
articlenumber: '1',
name: 'Super Awesome Product',
category: 'magic'
}
2nd Object:
productDetails = {
articlenumber: '1',
color: 'red',
size: 'xxl'
}
My wish:
products = {
articlenumber: '1',
name: 'Super Awesome Product',
category: 'magic',
details: {
color: 'red',
size: 'xxl'
},
},
{
...
}
What I did:
var merged = _.merge(_.keyBy(products, 'articlenumber'), _.keyBy(productDetails, 'articlenumber'));
What I got:
products = {
articlenumber: '1',
name: 'Super Awesome Product',
category: 'magic',
color: 'red',
size: 'xxl'
},
{
...
}
If Products is indeed an object, #Erfan's answer is gold.
Assuming Products is an array, you can try:
products = [{
articlenumber: '1',
name: 'Super Awesome Product',
category: 'magic'
}]
productDetails = [{
articlenumber: '1',
color: 'red',
size: 'xxl'
}]
products.forEach((prod) => {
var productDetail = productDetails.filter((prodDetail) => {return prod['articlenumber']===prodDetail['articlenumber']})[0]
var productToAdd = {}
var fields = ['color', 'size']
fields.forEach((field) => productToAdd[field] = productDetail[field])
prod['details'] = productToAdd
});
console.log(products)
There are many ways to merge:
To merge:
Please read the links below:
How can I merge properties of two JavaScript objects dynamically?
To remove duplicates:
How to remove all duplicates from an array of objects?
let products = {
articlenumber: '1',
name: 'Super Awesome Product',
category: 'magic'
};
let productDetails = {
articlenumber: '1',
color: 'red',
size: 'xxl'
};
let merged = {...products,details: productDetails};
console.log(merged);

Sort complex javascript objects based on property value of another list of objects inside main object

I have list of objects in the following structure which are already in sorted order by name property in the top level.
[{
name: 'name1'
team: 'team1'
statuses: [{ time: 'day1', color: 'green', message: 'looks good'}, { time: 'day2', color: 'green', message: 'looks good'}]
},
{
name: 'name2'
team: 'team2'
statuses: [{ time: 'day1', color: 'yellow', message: 'mild concern'}, { time: 'day2', color: 'red', message: 'critical issue'}]
},
{
name: 'name3'
team: 'team3'
statuses: [{ time: 'day1', color: 'orange', message: 'mild concern'}, { time: 'day2', color: 'orange', message: 'potential issue'}]
}]
The above list should be sorted with custom sort order(red, orange, green) based on color property of last object in the status list. Expected list contains objects in this order team2, team3, team1, if there are multiple of same color then it should retain sorted of name property at top level.
I tried using reduce function in the following way and combined all of them together, but not getting expected output.
teams.reduce((r, t) => {
if(t.statuses[1].color === 'red');
r.push(t)
return r;
}, { [] })
teams.reduce((r, t) => {
if(t.statuses[1].color === 'orange');
r.push(t)
return r;
}, { [] })
teams.reduce((r, t) => {
if(t.statuses[1].color === 'green');
r.push(t)
return r;
}, { [] })
Use filter on the original-array, for sorting order I use a COLORS-array. I added color "yellow" at the end because it was not mentioned in the sort-criterium, you can handle it to your choice.
Extended:
As wished is yellow now sorted between orange and yellow.
If it is green and comment is other than "looks good" then it should come at beginning.
let list = [{
name: 'name1',
team: 'team1',
statuses: [{ time: 'day1', color: 'green', message: 'looks good'}, { time: 'day2', color: 'green', message: 'looks good'}]
},
{
name: 'name2',
team: 'team2',
statuses: [{ time: 'day1', color: 'yellow', message: 'mild concern'}, { time: 'day2', color: 'red', message: 'critical issue'}]
},
{
name: 'name3',
team: 'team3',
statuses: [{ time: 'day1', color: 'orange', message: 'mild concern'}, { time: 'day2', color: 'orange', message: 'potential issue'}]
},
{
name: 'name4',
team: 'team4',
statuses: [{ time: 'day1', color: 'yellow', message: 'mild concern'}, { time: 'day2', color: 'green', message: 'potential issue'}]
}
];
const COLORS = ['red', 'orange', 'yellow', 'green'];
const GREEN = COLORS.indexOf('green');
let result = list.sort((a,b) => {
let stata = a.statuses[a.statuses.length-1];
let statb = b.statuses[b.statuses.length-1];
let cola = COLORS.indexOf(stata.color);
let colb = COLORS.indexOf(statb.color);
if (cola == GREEN && stata.message != 'looks good') {
return (colb == GREEN && statb.message != 'looks good') ? a.name.localeCompare(b.name) : -1;
}
if (colb == GREEN && statb.message != 'looks good') {
return 1;
}
return (cola < colb) ? -1 : ((cola > colb) ? 1: a.name.localeCompare(b.name));
});
console.log(result);
You could create one object where you define order of colors and then use sort method where you first sort by colors and if the colors are the same then you sort by name
const data = [{"name":"name1","team":"team1","statuses":[{"time":"day1","color":"green","message":"looks good"},{"time":"day2","color":"green","message":"looks good"}]},{"name":"name2","team":"team2","statuses":[{"time":"day1","color":"yellow","message":"mild concern"},{"time":"day2","color":"red","message":"critical issue"}]},{"name":"name3","team":"team3","statuses":[{"time":"day1","color":"orange","message":"mild concern"},{"time":"day2","color":"orange","message":"potential issue"}]}]
const order = {
red: 1,
orange: 2,
green: 3
}
data.sort((a, b) => {
const aColor = a.statuses.slice(-1)[0].color;
const bColor = b.statuses.slice(-1)[0].color;
return order[aColor] - order[bColor] || a.name.localeCompare(b.name)
})
console.log(data)

From an array of objects, collect and remove duplicates for all attributes in JavaScript

Given a set of data like:
[
{ name: 'apple', color: 'red', hasPeel: false },
{ name: 'banana', color: 'yellow', hasPeel: true },
{ name: 'orange', color: 'orange', hasPeel: true },
{ name: 'strawberry', color: 'red', hasPeel: false }
]
I would like to get an object like:
{
name: ['apple', 'banana', 'orange', 'strawberry'],
color: ['red', 'yellow', 'orange'],
hasPeel: [true, false]
}
(Grouped by properties, with duplicates removed)
What would be the most efficient way to achieve this?
I've been playing around with different ES6 tricks and lodash helpers, but haven't found an efficient way to only iterate over the array once.
You could do this with reduce() and forEach() methods to build object and ES6 Set to remove duplicate values from arrays.
const data = [{"name":"apple","color":"red","hasPeel":false},{"name":"banana","color":"yellow","hasPeel":true},{"name":"orange","color":"orange","hasPeel":true},{"name":"strawberry","color":"red","hasPeel":false}]
const result = data.reduce((r, e) => {
Object.keys(e).forEach(k => {
if (!r[k]) r[k] = [e[k]]
else r[k] = [...new Set(r[k]).add(e[k])]
});
return r;
}, {})
console.log(result)
You could write this in shorter form like this.
const data = [{ name: 'apple', color: 'red', hasPeel: false },{ name: 'banana', color: 'yellow', hasPeel: true },{ name: 'orange', color: 'orange', hasPeel: true },{ name: 'strawberry', color: 'red', hasPeel: false }]
const result = data.reduce((r, e) => {
Object.keys(e).forEach(k => r[k] = [...new Set(r[k]).add(e[k])]);
return r;
}, {})
console.log(result)
const input = [{
name: 'apple',
color: 'red',
hasPeel: false
},
{
name: 'banana',
color: 'yellow',
hasPeel: true
},
{
name: 'orange',
color: 'orange',
hasPeel: true
},
{
name: 'strawberry',
color: 'red',
hasPeel: false
}
];
const result = {};
for (const el of input) {
for (const [key, value] of Object.entries(el)) {
if (!result[key]) {
result[key] = [value];
} else if (!result[key].includes(value)) {
result[key].push(value);
}
}
}
console.log(result)
An alternative is using the functions reduce, forEach to group and function includes to check for duplicated.
var array = [{ name: 'apple', color: 'red', hasPeel: false },{ name: 'banana', color: 'yellow', hasPeel: true },{ name: 'orange', color: 'orange', hasPeel: true },{ name: 'strawberry', color: 'red', hasPeel: false }];
var result = array.reduce((a, c) => {
Object.keys(c).forEach(k => {
if (!(a[k] || (a[k] = [])).includes(c[k])) a[k].push(c[k]);
});
return a;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It would be nice to see what you have tried along with the problem/s you were having, and why you found your solution/s inefficient. Anyway, here is yet another solution, it requires at least ES2018 or you can use babel to transpile it.
const data = [{
name: "apple",
color: "red",
hasPeel: false
},
{
name: "banana",
color: "yellow",
hasPeel: true
},
{
name: "orange",
color: "orange",
hasPeel: true
},
{
name: "strawberry",
color: "red",
hasPeel: false
}
];
const x = [...data.reduce((map, object) => {
for (let [key, value] of Object.entries(object)) {
if (map.has(key)) {
map.get(key).add(value);
} else {
map.set(key, new Set([value]));
}
}
return map;
}, new Map())].reduce((object, [key, set]) => ({
...object,
[key]: [...set]
}), {});
console.log(x);

Filter by multiple keys and values, Javascript

I'm trying to make a filter. The number of filters will change dynamically, a number of keys can be different, and the number of values, too.
This is how data look like:
var data = [
{id: "123", color: "Red", model: "Tesla"},
{id: "124", color: "Black", model: "Honda"},
{id: "125", color: "Red", model: "Audi"},
{id: "126", color: "Blue", model: "Tesla"}]
Filter keys are color and model. But sometimes I will filter only by color or model and sometimes by both. I want to make a function that will cover both cases. Also, a user can choose many values (Tesla, Honda...).
Key can be only color, or only model, or both.
Values can look like: only "Red", "Red" and "Blue", or "Red" and "Tesla", or "Red", "Blue" and "Tesla"... Depends on what user choose.
I tried this:
var filtered = [];
data.forEach(item => {
filterByKey.forEach(key => {
values.forEach(value => {
if (item[key] === value) {
filtered.push(item);
}
});
});
});
Here is JsFiddle
My loop works well when I have one filter key, but it doesn't work well when I have more than one key. Is it a good idea to pass keys and values as an array?
No jQuery please, only pure JavaScript.
You can use filter() with every() and check if value of current object with current key exits in values array using includes()
var data = [{"id":"123","color":"Red","model":"Tesla"},{"id":"124","color":"Black","model":"Honda"},{"id":"125","color":"Red","model":"Audi"},{"id":"126","color":"Blue","model":"Tesla"}]
var keys = ["color", 'model'];
var values = ["Tesla", "Audi", "Red"];
var result = data.filter(function(e) {
return keys.every(function(a) {
return values.includes(e[a])
})
})
console.log(result);
You could use a combined approach with a seach object which keeps the conditions, like
{
model: 'Tesla', // a single value
color: ['red', 'blue'], // a some value
price: { // a range/interval
min: 2000,
max: 3000
},
transmission: v => v.toLowerCase() === 'automatic' // a function
}
var useConditions = search => a => Object.keys(search).every(k =>
a[k] === search[k] ||
Array.isArray(search[k]) && search[k].includes(a[k]) ||
typeof search[k] === 'object' && +search[k].min <= a[k] && a[k] <= +search[k].max ||
typeof search[k] === 'function' && search[k](a[k])
),
data = [{ id: "123", color: "Red", model: "Tesla" }, { id: "124", color: "Black", model: "Honda" }, { id: "125", color: "Red", model: "Audi" }, { id: "126", color: "Blue", model: "Tesla" }],
filters = { color: ['Red', 'Blue'], model: 'Tesla' };
console.log(data.filter(useConditions(filters)));
You can use Array.prototype.filter() where the function to test each element of the array is:
el => !!filterBy.toString().match(new RegExp(`(?=.*${el.color})(?=.*${el.model})`))
It consist of a regular expression new RegExp(`(?=.*${el.color})(?=.*${el.model})`)) that match to strings color and model in another string filterBy.toString()
var data = [{id: "123", color: "Red", model: "Tesla"}, {id: "124", color: "Black", model: "Honda"}, {id: "125", color: "Red", model: "Audi"}, {id: "126", color: "Blue", model: "Tesla"}],
filterBy = ['Tesla', 'Audi', 'Red', 'Black'],
result = data.filter(el => !!filterBy.toString().match(new RegExp(`(?=.*${el.color})(?=.*${el.model})`)));
console.log(result);
And also, you can combine Array.prototype.filter() and Array.prototype.includes():
var data = [{id: "123", color: "Red", model: "Tesla"}, {id: "124", color: "Black", model: "Honda"}, {id: "125", color: "Red", model: "Audi"}, {id: "126", color: "Blue", model: "Tesla"}],
filterBy = ['Tesla', 'Audi', 'Red', 'Black'],
result = data.filter(el => filterBy.includes(el.model) && filterBy.includes(el.color));
console.log(result);
For a case like this here's what I just do:
function _filter(data, query) { // query = "Red" or "Tesla"
return data.filter(el => {
return el.model.toLowerCase().indexOf(query.toLowerCase()) !== -1 ||
el.name.toLowerCase().indexOf(query.toLowerCase()) !== -1;
});
}
Now this function searches data on the model key and name key.
Is this what you want to achieve?
var data = [
{id: "123", color: "Red", model: "Tesla"},
{id: "124", color: "Black", model: "Honda"},
{id: "125", color: "Red", model: "Audi"},
{id: "126", color: "Blue", model: "Tesla"}
];
var allowedColors = ["Red", "Blue"];
var allowedModels = ["Tesla", "Audi"];
function filter (cars, colors, models) {
return cars.filter(function (car) {
return colors.indexOf(car.color) !== -1 && // check if the car's color is allowed
models.indexOf(car.model) !== -1; // check if the car's model is allowed
});
}
var filtered = filter(data, allowedColors, allowedModels);
console.log(filtered);
Filtering is native in JS, try this:
data.filter(item => item.color==='Red');
// or in the old fashion
data.filter(function(item){ return item.color==='Red';} );
Here, I have a different scenario.
I have an object
d={r1: {name: "r1", validation: true}, r2: {name: "r2", validation: true}};
and another array
a = ["r1"];
Here, the result I want is
d={r1: {name: "r1", validation: true}}
Using filter() to filter data, combined with some() to check if multiple keys match at least one of the strings from the values using includes()
// array --------------------
const data = [
{ id: '123', color: 'Red', model: 'Tesla' },
{ id: '124', color: 'Black', model: 'Honda' },
{ id: '125', color: 'Red', model: 'Audi' },
{ id: '126', color: 'Blue', model: 'Tesla' },
];
// filter exact match --------------------
const keysExact = ['color', 'model'];
const valuesExact = ['Tesla', 'Audi', 'Red'];
const resultExact = data.filter((item) =>
keysExact.every((a) => valuesExact.includes(item[a]))
);
console.log(resultExact);
// result:
// [
// { id: '123', color: 'Red', model: 'Tesla' },
// { id: '125', color: 'Red', model: 'Audi' },
// ]; 
Using filter() to filter data, combined with some() to check if multiple keys contain at least one of the strings from the values using includes().
// array --------------------
const data = [
{ id: '123', color: 'Red', model: 'Tesla' },
{ id: '124', color: 'Black', model: 'Honda' },
{ id: '125', color: 'Red', model: 'Audi' },
{ id: '126', color: 'Blue', model: 'Tesla' },
];
// filter data by keys containing values string (at least one of the string in values) --------------------
const keysSome = ['color', 'model'];
const valuesSome = ['Tes', 're'];
const resultSome = data.filter((item) =>
keysSome.some((key) =>
valuesSome.some((val) => item[key].toLowerCase().includes(val.toLowerCase()))
)
);
console.log(resultSome);
// result:
// [
// { id: '123', color: 'Red', model: 'Tesla' },
// { id: '125', color: 'Red', model: 'Audi' },
// { id: '126', color: 'Blue', model: 'Tesla' },
// ];
Using filter() to filter data, combined with every() to check if multiple keys contain all strings from the values using includes().
// array --------------------
const data = [
{ id: '123', color: 'Red', model: 'Tesla' },
{ id: '124', color: 'Black', model: 'Honda' },
{ id: '125', color: 'Red', model: 'Audi' },
{ id: '126', color: 'Blue', model: 'Tesla' },
];
// filter data by keys containing values string (matches all strings from values) --------------------
const keysEvery = ['color', 'model'];
const valuesEvery = ['Tes', 're'];
const resultEvery = data.filter((item) =>
keysEvery.every((key) =>
valuesEvery.some((val) => item[key].toLowerCase().includes(val.toLowerCase()))
)
);
console.log(resultEvery);
// result:
// [
// { id: '123', color: 'Red', model: 'Tesla' }
// ]

Categories