How to extract a sample of objects from array of nested objects - javascript

i'm developing in Javascript and i created folders array that contain objects:
folders = [folder1, folder2, folder3...]
Every object have some properties, one of this is docs that is an array of objects:
docs = [doc1, doc2, doc3...]
...and every object is like this:
doc1.title = 'foo'
doc1.desc = 'bar'
doc1.attr = {new: _.random(0, 1) > 0.5, read: _.random(0, 1) > 0.5}
...
I would like to create a function that extract only docs that have attr = {new: true, read: false}.
I tried some underscore method such as _.each, _.sample, _.find and _.findWhere, but i can't figure out how to get from the main array a sample that contains docs with that attr properties.
Any idea?

Using underscore first flatten the folders and then use where to get what you want:
var result = _.where( _.flatten(folders), {new: true, read: false});
Edited to work with the new structure:
var result = _.chain(folders)
.pluck('docs')
.flatten()
.where({isNew: true, read: false})
.value();
var folders = [
{
docs: [
{
title: 'one',
isNew: true,
read: false
}, {
title: 'two',
isNew: true,
read: true
}
]
},
{
docs:
[
{
title: 'three',
isNew: false,
read: false
}, {
title: 'four',
isNew: true,
read: false
}
]
}
];
var result = _.chain(folders)
.pluck('docs')
.flatten()
.where({isNew: true, read: false})
.value();
document.getElementById('result').textContent = JSON.stringify(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore.js"></script>
<p>
<pre id="result"></pre>
</p>

Have you tried something like this?
var result = [];
folders.forEach(function(docs) {
result.concat( docs.filter(function(doc) {
return doc.attr.new && !doc.attr.read;
});
});

underscore methods like .find work on arrays which are one level deep:
var temp = [];
_.each(folders , function(docsArray){
var result = _.where(docsArray , {new: true, read: false});
if(result){
temp.concat(result);
}
});

You can achieve what you want without using underscore or lodash,just
Take advantage of the built-in Array.map and Array.filter methods, here is an example :
var myResult = folders.map(function(folder){
return myCustomDocs = folder.filter(function(doc){
return (doc.attr.new && !doc.attr.read);
});
});
console.log(myResult);

What about a regular filter?
Working Demo
(Simply open your browser console and run the fiddle.)
doc1.filter(o => o.attr.new && !o.attr.read)
And you can also simply map() your folders array.
const _folders = folders
.map(doc => doc.filter(o => o.attr.new && !o.attr.read))
You would get on new folders array containing arrays with only new && !read documents. Which you could flatten if needed:
const flatFolders = [].concat.apply([], _folders)

I found a working solution.
Here is my code:
var result = [];
folders.forEach(function(item) {
result = result.concat(_.filter(item.docs, function(doc) {
return doc.isNew === true && doc.read === false;
}));
});

If you have Node.js 5+ or use Babel, you can just do:
folders.reduce((res, arr) => res.concat(arr), []) // flatten folders
.filter(doc => doc.attr.new && !doc.attr.read); // remove all that don't return true

Related

typescript syntax - how to single line filter/map array [duplicate]

I have an array of objects that I want to iterate over to produce a new filtered array. But also, I need to filter out some of the objects from the new array depending of a parameter. I'm trying this:
function renderOptions(options) {
return options.map(function (option) {
if (!option.assigned) {
return (someNewObject);
}
});
}
Is that a good approach? Is there a better method? I'm open to use any library such as lodash.
You should use Array.reduce for this.
var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
var reduced = options.reduce(function(filtered, option) {
if (option.assigned) {
var someNewValue = { name: option.name, newProperty: 'Foo' }
filtered.push(someNewValue);
}
return filtered;
}, []);
document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>
Alternatively, the reducer can be a pure function, like this
var reduced = options.reduce(function(result, option) {
if (option.assigned) {
return result.concat({
name: option.name,
newProperty: 'Foo'
});
}
return result;
}, []);
Since 2019, Array.prototype.flatMap is good option.
options.flatMap(o => o.assigned ? [o.name] : []);
From the MDN page linked above:
flatMap can be used as a way to add and remove items (modify the
number of items) during a map. In other words, it allows you to map
many items to many items (by handling each input item separately),
rather than always one-to-one. In this sense, it works like the
opposite of filter. Simply return a 1-element array to keep the item,
a multiple-element array to add items, or a 0-element array to remove
the item.
Use reduce, Luke!
function renderOptions(options) {
return options.reduce(function (res, option) {
if (!option.assigned) {
res.push(someNewObject);
}
return res;
}, []);
}
With ES6 you can do it very short:
options.filter(opt => !opt.assigned).map(opt => someNewObject)
I'd make a comment, but I don't have the required reputation. A small improvement to Maxim Kuzmin's otherwise very good answer to make it more efficient:
const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const filtered = options
.reduce((result, { name, assigned }) => assigned ? result.push(name) && result : result, []);
console.log(filtered);
Explanation
Instead of spreading the entire result over and over for each iteration, we only append to the array, and only when there's actually a value to insert.
One line reduce with ES6 fancy spread syntax is here!
var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const filtered = options
.reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);
console.log(filtered);
At some point, isn't it easier(or just as easy) to use a forEach
var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
var reduced = []
options.forEach(function(option) {
if (option.assigned) {
var someNewValue = { name: option.name, newProperty: 'Foo' }
reduced.push(someNewValue);
}
});
document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>
However it would be nice if there was a malter() or fap() function that combines the map and filter functions. It would work like a filter, except instead of returning true or false, it would return any object or a null/undefined.
Use Array.prototype.filter:
function renderOptions(options) {
return options.filter(function(option){
return !option.assigned;
}).map(function (option) {
return (someNewObject);
});
}
I optimized the answers with the following points:
Rewriting if (cond) { stmt; } as cond && stmt;
Use ES6 Arrow Functions
I'll present two solutions, one using forEach, the other using reduce:
Solution 1: Using forEach
The solution works by using forEach to iterate through every element. Then, in the body of the forEach loop, we have the conditional to act as a filter and it determines whether we are going to append something to the result array.
const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const reduced = [ ];
options.forEach(o => {
o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);
Solution 2: Using reduce
This solution uses Array.prototype.reduce instead of forEach to iterate through the array. It recognizes the fact that reduce has both an initializer and a looping mechanism built in. Other than that, this solution is more or less the same as the forEach solution, so, the difference comes down to cosmetic syntax sugar.
const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const reduced = options.reduce((a, o) => {
o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
return a;
}, [ ] );
console.log(reduced);
I leave it up to you to decide which solution to go for.
Using reduce, you can do this in one Array.prototype function. This will fetch all even numbers from an array.
var arr = [1,2,3,4,5,6,7,8];
var brr = arr.reduce((c, n) => {
if (n % 2 !== 0) {
return c;
}
c.push(n);
return c;
}, []);
document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>
You can use the same method and generalize it for your objects, like this.
var arr = options.reduce(function(c,n){
if(somecondition) {return c;}
c.push(n);
return c;
}, []);
arr will now contain the filtered objects.
I've covert these great answers into utility functions and I'd like to share them:
Example: filter only odd numbers and increment it
e.g. [1, 2, 3, 4, 5] -filter-> [1, 3, 5] -map-> [2, 4, 6]
Normally you'd do it like this with filter and map
const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = inputArray.filter((item) => item % 2).map((item) => item + 1); // [ 2, 4, 6 ]
Using reduce
const filterMap = <TSource, TTarget>(
items: TSource[],
filterFn: (item: TSource) => boolean,
mapFn: (item: TSource) => TTarget
) =>
items.reduce((acc, cur): TTarget[] => {
if (filterFn(cur)) return [...acc, mapFn(cur)];
return acc;
}, [] as TTarget[]);
Using flatMap
const filterMap = <TSource, TTarget>(
items: TSource[],
filterFn: (item: TSource) => boolean,
mapFn: (item: TSource) => TTarget
) => items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));
Usage (same for both reduce and flatMap solution):
const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = filterMap(
inputArray,
(item) => item % 2, // Filter only odd numbers
(item) => item + 1 // Increment each number
); // [ 2, 4, 6 ]
JavaScript version
The above codes are in TypeScript but the question asks about JavaScript. So, I've remove all the generics and types for you:
const filterMap = (items, filterFn, mapFn) =>
items.reduce((acc, cur) => {
if (filterFn(cur)) return [...acc, mapFn(cur)];
return acc;
}, []);
const filterMap = (items, filterFn, mapFn) =>
items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));
Direct use of .reduce can be hard to read, so I'd recommend creating a function that generates the reducer for you:
function mapfilter(mapper) {
return (acc, val) => {
const mapped = mapper(val);
if (mapped !== false)
acc.push(mapped);
return acc;
};
}
Use it like so:
const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
.reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags); // ['javascript', 'arrays'];
You can use Array.reduce with an arrow function is a single line of code
const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const reduced = options.reduce((result, option) => option.assigned ? result.concat({ name: option.name, newProperty: 'Foo' }) : result, []);
document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>
The most efficient way of doing filter + map at once is to process data as a generic iterable, and do both things at once. In this case, you will end up going through data once, at most.
The example below is using iter-ops library, and doing exactly that:
import {pipe, filter, map} from 'iter-ops';
const i = pipe(
inputArray,
filter(value => value === 123), // filter on whatever key you want
map(value => /* any mapping here*/) // remap data as you like
);
// i = iterable that can be processed further;
console.log([...i]); //=> list of new objects
Above, I was saying at most, because if you apply further logic to the iterable result, like limit the number of mapped items, for example, you will end up iterating through your list of objects even less than once:
const i = pipe(
inputArray,
filter(value => value === 123), // filter on whatever key you want
map(value => /* any mapping here*/), // remap as you like
take(10) // take up to 10 items only
);
Above, we limit iteration further, to stop once 10 resulting items have been generated, and so we are iterating through data less than once. That's as efficient as it gets.
UPDATE
I was asked to add to the answer why this solution is more efficient than reduce, and so here it is...
Array's reduce is a finite operation, which goes through the complete set of data, in order to produce the result. So when you need to do further processing on that output data, you will end up producing a new iteration sequence, and so on.
When you have a complex business logic to be applied to a sequence/iterable, it is always much more efficient to chain that logic, while iterating through the sequence just once. In many cases, you will end up doing complex processing on a sequence, without going through the complete set of data even once. That's the efficiency of iterable data processing.
P.S. I'm the author of the aforesaid library.
Hey I've just worked on this project and wanted to share my solution based on Array.prototype.flatMap() in MDN docs:
const places=[{latitude:40,longitude:1},{latitude:41,longitude:2},{latitude:44,longitude:2},{latitude:NaN,longitude:NaN},{latitude:45,longitude:4},{latitude:48,longitude:3},{latitude:44,longitude:5},{latitude:39,longitude:13},{latitude:40,longitude:8},{latitude:38,longitude:4}];
let items = places?.map((place) => [{
latitude: (place.latitude),
longitude: (place.longitude),
}, ]);
console.log("Items: ", items);
//Remove elements with NaN latitude and longitude
let newItems = places?.flatMap((o) =>
Number(o.longitude, o.latitude) ?
{
lng: Number(o.longitude),
lat: Number(o.latitude)
} :
[]
);
console.log("Coordinates after NaN values removed: ", newItems);
Same approach as the top answers, using Array.prototype.reduce(), but with updated ES6 syntax, and TypeScript typings, as a generic utility function:
function filterThenMap<T>(l: T[], predicate: (el: T) => boolean, transform: (el: T) => T) {
return l.reduce((res: T[], el) => {
if (predicate(el)) {
res.push(transform(el));
}
return res;
}, []);
}

JS if key exist, include to array

I'm stuck in mapping object to array.
I use map, but its add every object field in array and i got a lot of undefined.
const mapKey: { [key: string]: number } = {
'hello': 3,
};
preferences = {
hello: true,
.....
.....
}
const array = Object.entries(preferences).map(([key, value]) => {
return mapKey[key] && { index: mapKey[key], visible: true };
});
result is:
[undefined, undefined....{ index: mapKey[key], visible: true }]
but i need just [{ index: mapKey[key], visible: true }]
The Array#map method generates an array based on return value, it's not suited for requirement so use Array#reduce method.
const array = Object.entries(preferences).reduce((arr, [key, value]) => {
// push into the array only if defined
columnIndexMap[key] && arr.push({ index: mapKey[key], visible: true });
// return the array reference for next iteration
return arr;
// set initial value as empty array for the result
}, []);
One-liner solution:
const array = Object.entries(preferences).reduce((arr, [key, value]) => (columnIndexMap[key] && arr.push({ index: mapKey[key], visible: true }), arr), []);
The answer with reduce of course works and is efficient. You can also use filter() + map. This has the disadvantage of looking at values twice, but has a readability advantage. Which is more important, of course, depends on your use-case. I would prefer the following unless there is so much data that the additional work is noticeable:
const mapKey = { 'hello': 3, 'test':4, 'test2': 5};
let preferences = { hello: true, test:false, test2:true}
let filtered = Object.entries(preferences)
.filter(([k, visible]) => visible)
.map(([k, visible]) => ({ index: mapKey[k], visible }))
console.log(filtered)

Looping through an array of objects in typescript and get a specific fails using Es6 syntax

I have several objects and i would like to get one and check a specific property
so i have
data: [{is_guest: true},{permission:'is_allowed_ip'}]
Now when i check the console.log(route.data) am getting
0:{is_guest:true},
1:{permission:'is_allowed_ip' }
and typeof route.data is an object
now i would like to get the object with is_guest:true
So i have tried
const data = Object.keys(route.data).map((index) => {
if (route.data[index].is_guest) {
return route.data[index]
}
});
console.log("route data is",data) //this still returns all the items
But the above fails returning all the objects.
How do i loop through all the objects and retrieve just only one with the is_guest key and value true
Sounds like you want Object.values, not Object.keys, and filter:
const data = Object.values(route.data).filter(e => e.is_guest);
Object.values is fairly new, but present on up-to-date Node, and entirely polyfillable.
Example:
const route = {
data: [
{is_guest: true},
{permission:'is_allowed_ip'}
]
};
const data = Object.values(route.data).filter(e => e.is_guest);
console.log(data);
Using E6:
data.filter(o => o.is_guest)
You can use the filter method.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I added some ids into your array just to make easier to understand.
// added ids to exemplify
const data = [
{id: 1, is_guest: true},
{id: 2, permission:'is_allowed_ip'},
{id: 3, is_guest: true},
{id: 4, is_guest: false},
]
// filter results
const filtered = data.filter(item => item.is_guest)
// just to show the result
document.querySelector('.debug').innerHTML = JSON.stringify(filtered, null, 2);
<pre><code class="debug"></code></pre>

Get object keys based on value

I have a use case where I have an object of varying values, and I need to get all of these keys that have a specific value. For instance, here is a sample object:
myObject = {
Person1: true,
Person2: false,
Person3: true,
Person4: false
};
The key names will vary, but the valid values are true or false. I want to get an array of the names that have a value of true:
myArray2 = [
'Person1',
'Person3
];
I've been trying to use various lodash functions in combination such as _.key() and _.filter, but with no luck. How can I accomplish this? I'm open to pure JS or Lodash options.
UPDATE: I accepted mhodges' answer below as the accepted answer, although others gave me the same answer. Based on that, I came up with a Lodash version:
var myArray = _(myObject).keys().filter(function(e) {
return myObject[e] === true;
}).value();
If I understand your question correctly, you should be able to use basic .filter() for this.
myObject = {
Person1: true,
Person2: false,
Person3: true,
Person4: false
};
var validKeys = Object.keys(myObject).filter(function (key) {
return myObject[key] === true;
});
Since Lodash was tagged: With pickBy the values can be filtered (and the keys obtained with _.keys ):
var myArray2 = _.keys(_.pickBy(myObject));
var myObject = { Person1: true, Person2: false, Person3: true, Person4: false };
var myArray2 = _.keys(_.pickBy(myObject));
console.log(myArray2 );
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Use Object.keys():
var object = {
1: 'a',
2: 'b',
3: 'c'
};
console.log(Object.keys(object));
Alternative solution:
var keys = [];
for (var key in object) {
if (object.hasOwnProperty(key)) {
keys.push(key);
}
}
console.log(keys);
Don't forget to check a key with the help of hasOwnProperty(), otherwise this approach may result in unwanted keys showing up in the result.
You can do this with Object.keys() and filter().
var myObject = {
Person1: true,
Person2: false,
Person3: true,
Person4: false
};
var result = Object.keys(myObject).filter(function(e) {
return myObject[e] === true;
})
console.log(result)
ES6 version with arrow function
var result = Object.keys(myObject).filter(e => myObject[e] === true)

How to use Lodash to make this mapping simpler?

I Have the following object:
var config = {
apps: false,
transfers: false,
approvals: true
};
I want to know how I can use Lodash to go through each of the keys within the config object and find out which key has the first occurrence of value true. In the example above, I expect the output to be approvals. If the value for transfers was true, I expect the output to be transfers.
Basically, I want a more cleaner way to do this:
if (config.apps) {
answer = 'apps';
} else if (config.transfers) {
answer = 'transfers';
} else if (config.approvals) {
answer = 'approvals';
}
Thanks!
You can use _.findKey():
var config = { apps: false, transfers: false, approvals: true };
var answer = _.findKey(config);
console.log(answer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
Or without lodash, you can use Array.prototype.reduce() with Object.keys():
var config = { apps: false, transfers: false, approvals: true };
var answer = Object.keys(config).reduce(function(answer, key) {
return value = config[key] ? key : answer;
}, '');
console.log(answer);
No lodash required:
var config = {
apps: false,
transfers: false,
approvals: true
};
Object.keys(config).find((key) => {
return config[key];
});
A bit newer method then Object.keys is entries with that you can retrieve both the key and the value
let config = {apps: false, transfers: false, approvals: true}
let [key, value] = Object.entries(config).find(([key, val]) => val)
console.log(key, value)

Categories