Deep reduce (pluck maybe?) of array of objects - javascript

I have lodash to use for this (or underscore). I am trying to take an array of objects and turn them into a boiled down array of objects. Let me show you.
$scope.myObject = [{
name: 'Name1',
subs: [
{
name: 'Sub 1',
subs: [
{
name: 'Sub 1-1',
apps: [
{
name: 'App 1'
}
]
}
],
apps: [
{
name: 'App'
}
]
}
That's the original objects (one node of it at least). And My desired effect is to boil it down to an array of objects that are just the 'apps'. As you see here the apps can fall on any level - so it needs to be some kind of a deep search/reduce. These objects can potentially go 10 levels deep and have an app array on any level. So I'm trying to boil it down to a flat array of just apps, so for example this object would turn into:
[{'name' : 'App 1'},{'name' : 'App'}];
I'm still pretty new to this kind of object manipulation so I could use some guidance. Thanks!

function pluckRecursive(input, prop, collect) {
collect = collect || [];
if (_.isArray(input)) {
_.forEach(input, function (value, key) {
pluckRecursive(value, prop, collect);
})
} else if (_.isObject(input)) {
_.forEach(input, function (value, key) {
if (key === prop) {
collect.push(value);
} else {
pluckRecursive(value, prop, collect);
}
})
}
return collect;
};
when used like
pluckRecursive($scope.myObject, 'apps')
returns:
[[{"name":"App 1"}], [{"name":"App"}]]
Use _.flatten() on that to get rid of the nested arrays.

You may use deep-reduce like this:
const deepReduce = require('deep-reduce')
let apps = deepReduce($scope.myObject, (arr, value, path) => {
if (path.match(/subs\.\d+\.apps\.\d+$/) {
arr.push(value)
}
return arr
}, [], 'subs')
Explanation of the code:
(arr, value, path) => ... is the reducer function. It should always return the accumulated value, here the array of apps. The accumulated value is passed to the reducer as the first argument.
value, is any value in your object tree. It may be the subs array, objects inside the subs array, or leaf values like subs.0.name.
path is where the current value passed is found. For example subs.0.subs.0.apps. Keys of arrays are digits, here 0.
subs\.\d+\.apps\.\d+$ matches any path that ends in subs.digit.apps.digit. I've omitted ^ from the start, since some apps are nested in subs subs. You may try out the regexp here: https://regex101.com/r/n1RfVv/1
[] is the initial value of arr.
'subs' tell deepReduce to start at that path, meaning that name or any other property in the root object will not be traversed.
Speed
Depending on the size of your objects, this might be slow, as deep-reduce traverses the whole object tree. If possible, define the starting path to limit traversal, like 'subs' is given in example above.
If your data source is not changing much, you might want to scrape the data source, clean it up, and provide your own api.
Inner workings
If you are interested in how deep-reduce works, you can read the code here: https://github.com/arve0/deep-reduce/blob/master/index.ts

Here are two solution using object-scan. The first one searches for apps anywhere, the second one only in nested subs.
// const objectScan = require('object-scan');
const myObject = [{ name: 'Name1', subs: [{ name: 'Sub 1', subs: [{ name: 'Sub 1-1', apps: [{ name: 'App 1' }] }], apps: [{ name: 'App' }] }] }];
console.log(objectScan(['**.apps[*]'], { rtn: 'value' })(myObject));
// => [ { name: 'App' }, { name: 'App 1' } ]
console.log(objectScan(['**(^subs$).apps'], { rtn: 'value', useArraySelector: false })(myObject));
// => [ { name: 'App' }, { name: 'App 1' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

You can do this natively using _.get
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
_.get(object, ['a', '0', 'b', 'c']);
// => 3
_.get(object, 'a.b.c', 'default');
// => 'default'

Related

Return all values of nested arrays using string identifier

Given an object searchable, is there a simple way of returning all the id values using lodash or underscore.js (or equivalent) where I can define the path to id?
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
I am looking to see if this is possible in a manner similar to how we can use lodash.get e.g. if we wanted to return the things array from searchable we could do
const things = _.get(searchable, 'things');
I can't seem to find anything similar in the documentation. I am looking for something
that could contain an implementation similar to:
_.<some_function>(searchable, 'things[].properties[].id')
Note: I am well aware of functions like Array.map etc and there are numerous ways of extracting the id property - it is this specific use case that I am trying to figure out, what library could support passing a path as a string like above or does lodash/underscore support such a method.
Found a solution using the package jsonpath
const jp = require('jsonpath');
const result = jp.query(searchable, '$.things[*].properties[*].id')
console.log(result);
// outputs: [ 'd1-i1', 'd1-i2', 'd2-i1', 'd2-i2' ]
you can do it easily in plain js
like this
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
const search = (data, k) => {
if(typeof data !== 'object'){
return []
}
return Object.entries(data).flatMap(([key, value]) => key === k ? [value]: search(value, k))
}
console.log(search(searchable, 'id'))
_.map and _.flatten together with iteratee shorthands let you expand nested properties. Every time you need to expand into an array, just chain another map and flatten:
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
// Let's say the path is "things[].properties[].id"
const result = _.chain(searchable)
.get('things').map('properties').flatten()
.map('id').value();
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/underscore#1.13.4/underscore-umd-min.js"></script>

Test array of objects for values equal to values from another array in Chai

I'm totally new to Chai, and this is probably really simple, but I can't find any reference to my case in the official docs. I'm trying to verify that an array with two objects has a property value exactly matching values in another array.
Consider the following:
test('Array includes ids', function() {
const array1 = [
{
type: 'type1',
id: '1'
},
{
type: 'type2',
id: '2'
},
]
const array2 = ['1', '2']
chai.expect(array1).to.have.deep.members(array2) // fails
})
I'd like to verify that objects in array1 has an id property with one of the values found in array2. I know I could map array1 as array1.map(p => p.id) and compare that with array2, but that seems to go against testing as a practice. After all, I don't want to tamper too much with my target to make it conform to my current level of knowledge.
Is there a better way to test this? (Needless to say, my example code above does not work as desired.)
Just transform your array1 with .map() method to have your kind of array with only ids in it just like array2,
test('Array includes ids', function() {
const array1 = [
{
type: 'type1',
id: '1'
},
{
type: 'type2',
id: '2'
},
]
const arr1 = array1.map(e=>e.id);//transform with .map() -> ['1', '2']
const array2 = ['1', '2']
chai.expect(arr1).to.have.deep.members(array2) // pass
})

How to get nested array item where object property id is "random" and change the name property

I have a nested array of objects. How to get nested array item where object property id is "random" and set name to "you won it"
The structure looks like this:
[
{
itemsBundle [
{
id: 'selection'
name: 'failed'
}
{
id: 'random'
name:'win'
}
]
basketId: 'item'
basketName
tags[{}{}]
}
{}
{}
]
I need somehow get the object inside the main array where nested itemsBundle array of objects containes object with id 'random' and then for that itemBundle's single object where id is 'random' set name from win to you won it. I thought about using nested map() with filter() or nested loops but not sure which option will be the best and how can this results be achieved with less complicated way. The only 3rd party library that I am using is lodash.
Working example with flatMap and find. BUT it is mutating the main content, you have to clone deep you list first.
Improve your question next time :)
const aa = [
{
itemsBundle: [
{
id: "selection",
name: "failed"
},
{
id: "random",
name: "win"
}
],
basketId: "item",
basketName: "",
tags: [{}, {}]
},
{},
{}
];
const items = aa.flatMap(a => a.itemsBundle || [])
const itemFound = items.find(item => item.id === 'random')
itemFound.name = 'you win'
console.log(aa[0].itemsBundle[1])

json object from javascript nested array

I'm using a nested array with the following structure:
arr[0]["id"] = "example0";
arr[0]["name"] = "name0";
arr[1]["id"] = "example1";
arr[1]["name"] = "name1";
arr[2]["id"] = "example2";
arr[2]["name"] = "name2";
now I'm trying to get a nested Json Object from this array
arr{
{
id: example0,
name: name00,
},
{
id: example1,
name: name01,
},
{
id: example2,
name: name02,
}
}
I tought it would work with JSON.stringify(arr); but it doesen't :(
I would be really happy for a solution.
Thank you!
If you are starting out with an array that looks like this, where each subarray's first element is the id and the second element is the name:
const array = [["example0", "name00"], ["example1", "name01"], ["example2", "name02"]]
You first need to map it to an array of Objects.
const arrayOfObjects = array.map((el) => ({
id: el[0],
name: el[1]
}))
Then you can call JSON.stringify(arrayOfObjects) to get the JSON.
You need to make a valid array:
arr = [
{
id: 'example0',
name: 'name00',
},
{
id: 'example1',
name: 'name01',
},
{
id: 'example2',
name: 'name02',
}
];
console.log(JSON.stringify(arr));
Note that I am assigning the array to a variable here. Also, I use [] to create an array where your original code had {}.

Lodash. How to groupBy except undefined

Trying to group up array of object to get unique properties with Lodash
But it takes undefined like unique property if property does not exists
Is there way how to avoid it? and left only existing properties ?
So i'm going to achieve only My Office 1 but with my solution getting
undefined and My Office 1
Example array
[
{office: null},
{office:
{
name: 'My Office 1'
}
}
]
code
Object.keys(_.groupBy(arr, 'office.name')).map((office, index) { ... }
You can just filter out the objects, which do not have the path.
let objects = [
{office: null},
{office: {name: 'My Office 1'}},
{office: {name: 'My Office 2'}},
{office: {name: 'My Office 1'}},
];
let path = 'office.name';
let grouped = _(objects)
.filter(object => _.has(object, path))
.groupBy(path)
.value();
console.log(grouped);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Categories

Resources