I'd like to select the first item from a nested Array, without fetching the whole document.
Schema/Model
Suppose I have a Schema like so:
const parentSchema = mongoose.Schema({
name: String,
children: []
});
const grandparentSchema = mongoose.Schema({
name: String,
children: [parentSchema]
})
Which would translate to this example instance:
{
name: 'Grandparent Foo',
children: [
{
name: 'Parent Foo',
children: ['Child Foo', 'Child Bar', 'Child Baz']
}
]
}
Question
I would like to get the first child of 'Parent Foo', so to boil it down I should be getting back 'Child Foo'
Notes
As you can see, the grandchildren are plain Strings, not Documents themselves (in contrast with the Parent) so I can't select them using dot notation.
I don't want to return the whole document and filter through it in code. I'd like to get over the wire only the first grandchild since the grandchildren Array (the children array of 'Parent Foo') can potentially contain millions of entries.
I need this because I want to $pop the first grandchild and return it. To do that, I plan on fetching the item first and then $pop it off, hence why I ask this question
You cannot really, without throwing extra work at the database.
As a general explanation:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
)
Will return just the matched entry from "children" and no others should they exist.
If you explicitly need the "first" array element, then you use .aggregate():
Granparent.aggregate([
{ "$match": { "children.name": "Parent Foo" } },
{ "$addFields": {
"children": {
"$map": {
"input": {
"$filter": {
"input": "$children",
"as": "child",
"cond": { "$eq": [ "$$child.name", "Parent Foo" ] }
}
},
"as": "child",
"in": {
"name": "$$child.name",
"children": { "$arrayElemAt": [ "$$child.children", 0 ] }
}
}
}
}}
])
So there you basically use $filter to replicate the standard positional match an then use $map to reshape with $arrayElemAt or $slice to actually get the first element of the inner array.
By contrast, if you live with returning "a small amount of extra data", then you just slice off of the positional match:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
).lean().exec((err,docs) => {
docs = docs.map( doc => {
doc.children = doc.children.map( c => c.children = c.children.slice(0,1) );
return doc;
});
// do something with docs
So we returned a little more in the cursor and just got rid of that very little bit of data with minimal effort.
Mileage may vary on this due to the actual size of real data, but if the difference is "small", then it's usually best to "trim" in the client rather than the server.
Related
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])
Assume we have two different structures from two different APIs. Each has a different schema.
We have this as a return from API #1
[
{
Id: "test1",
Title: "label 1",
Children: [
{
Id: "test2",
Title: "label 2",
Children: [
{
Id: "test3",
Title: "label 3"
}
]
}
]
}
]
I need to convert it to the following scheme:
[
{
value: "test1",
label: "label 1",
children: [
{
value: "test2",
label: "label 2",
children: [
{
value: "test3",
label: "label 3"
}
]
}
]
}
]
So far I have come up with this method:
const transformItem = ({ Id, Title, Children }) => ({
value: Id,
label: Title,
children: Children ? transformData(Children) : null
});
const transformData = arr => arr.map(item => transformItem(item));
// Process data
const DataForApi2 = transformData(DataFromApi1);
From the limited benchmarking I performed and from what I can tell, in V8 (which is 95+% of our userbase) this looks fast enough as I'm not mutating any data structure (ergo hot objects are intact and retain performance) and using everything under a scope so I don't waste memory. Seems to be of linear complexity and not too bad if only performed once per client loading the app (only the first time after login).
In terms of runtime you're right this is probably the fastest we can get with O(n).
You could improve your space complexity by converting your solution from recursive to iterative. It saves space on the callstack which helps in extreme cases where trees go extremely deep.
I am trying to find out if there any any es6 (or external library) ways to handle deep object comparison and parsing in JavaScript.
Take the following example, where I have a property history, which is an array, embedded within a property services, which is also an array:
{
_id: 4d39fe8b23dac43194a7f571,
name: {
first: "Jane",
last: "Smith"
}
services: [
{
service: "typeOne",
history: [
{ _id: 121,
completed: true,
title: "rookie"
},
{ _id: 122,
completed: false,
title: "novice"
}
]
},
{
service: "typeTwo",
history: [
{ _id: 135,
completed: true,
title: "rookie"
},
{ _id: 136,
completed: false,
title: "novice"
}
]
}
]
}
Now, say a new element is pushed onto the "history" array within the second "services" element, where (service : "typeTwo") -- on the "services" array. I need to identify that's happened, and pull out the entire parent element, because I also need to know what "service" within the "services" array had a new "history" element added.
Is there a way I can scan this entire object and not only determine when something's changed, but actually be able to pull out the section I need reference to? I'm open to either a native JS or JS library option here.
You can check for duplicates like this:
function isEqual(firstObject, secondObject) {
function _equals(firstObject, secondObject) {
let clone = {...{}, ...firstObject}, cloneStr = JSON.stringify(clone);
return cloneStr === JSON.stringify({...clone, ...secondObject});
}
return _equals(firstObject, secondObject) && _equals(secondObject, firstObject);
}
https://jsfiddle.net/b1puL04w/
If you considering libraries has stated, then lodash has _.isEqual which does perform a deep comparison between two values to determine if they are equal.
I have used it extensively for deep comparison in the past.
The Issue:
I'm attempting to build a simple search tool. It returns a search query by matching an id to another item with the same id. Without going into the complexities, the issue I'm having is that when my data was organized previously, the map function from javascript returned all the results perfectly. However, now that my data is structured a bit differently (a collection, I think?) ....the ids don't appear to be lining up which causes the wrong search results to show.
The function in question:
const options = this.props.itemIds.map((id) => (
<Option key={this.props.itemSearchList[id].id}>
{this.props.itemSearchList[id].name}
</Option>
));
When the data was structured like this it worked as expected:
Example of previous structure:
const items = [
{
id: 0,
name: "name 0",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 2,
name: "Name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
Now that the data is a ?collection...the map function doesn't work as anticipated and it returns improper results or none at all: I've been able to use the lodash Map function on this structure successfully in the past.
Here's a screenshot of the new data:
I believe a representative way to write out the example would be:
const newItems = [
0: {
id: 0,
name: "name here",
},
1: {
id: 1,
name: "name here",
},
]
Any recommendations for making this work or need more info? Perhaps I'm misunderstanding the issue entirely, but I believe it has to do with data structure and the map function from JS. I can see results returning, but the id's are not lining up appropriately anymore.
Here's a visual representation of the misalignment. The orange is the search input and it pulling the right result. The green is the misalignment of what it's actually showing because of the data structure and mapping (I assume).
The issue is you were using index and lining that up with id as a sort of pseudo-key which is...beyond fragile. What you should be doing is keying by id (meaing itemsshould be an object) and then having a seperate array that stores the order you want. So items would be an object keyed by id:
const items = {
1: {
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
2: {
id: 2,
name: "name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
9: {
id: 9,
name: "Name 9",
tags: ['#sports', '#outdoor', '#clothing'],
},
};
And then itemIds (which it appears you already have) is an array with the correct order:
const itemIds = [1,9,2];
And then they can be accessed in the right order by looping over that array, and getting the element by said key:
itemIds.map((id) => {
const item = items[id];
// do something with the item
}
Take a look at how Redux recommends normalizing state shape.
https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
What you call "collections" and "maps" are actually arrays. Now one of the arrays has the objects exactly at the position in the array that matches the id:
items[5].id === 5
Now through sorting /mutating / whatever you change the order so that the element at a certain position doesnt have that as an id:
newItems[5].id // 7 :(
That means that you cannot access the item that easy anymore, you now either have to sort the array again to bring it into order, or you search for an object with the id:
newItems.find(item => item.id === 5) // { id: 5, ... }
Or you switch over to some unsorted collections like a real Map:
const itemsMap = new Map(newItems.map(item => ([item.id, item])));
So you can get a certain item with its id as:
itemsMap.get(5) // { id: 5, ... }
... but the whole thing doesnt have to do with Array.prototype.map at all.
Here was my simple solution:
const options = [];
this.props.itemList.forEach((item) => {
if (this.props.searchResults.includes(item.id)) {
options.push(<Option key={item.id}>{item.name}</Option>);
}
});
Let me know what you think (to the group that tried to help!)
I have a json object something like this:
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
//.............. continues as curcular depndency
]
}
]
}
]
}
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
Due to this cicular dependency, JSON.stringify is failing.
I have done enough google and searching to get the solution for this but could not find much help.
So here basically I want to detect a circular dependency in the json object and add a new key to the object, saying cricular: true and remove all the subsequent node.
So here is the result output what I am looking :
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
circular: true,
children: [ // No children here as it is curcular dependency
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
There is a way, which I think can solve it, where I can loop through all the children unless there is no children upto maximum 2 levels, but that way I will miss valid children which are having depth more than 3.
I hope my question is clear. If not please let me know I will try to expand this further.
A recursive function solves this:
function check(stack,parent, obj){
stack = stack || []; //stack contains a list of all previously occurred names
var found = stack.find(function(parent){
return (parent==obj.name && obj.children.length>0); //checks to see if the current object name matches any in the stack.
});
if(!found && obj.children.length>0){
stack.push(obj.name); //adds the current object name to the list.
obj.children.forEach(function(child){
check(stack,obj, child);//recursively checks for all children.
})
}
else if(found){
parent.children=[];
parent.circular=true;
stack.pop(obj.name);
return;
}
else{
return;
}
}
check([],temp1, temp1)
This leads to alteration of the original object passed.
Hope this helps!
use console.table(circularObj) to help you in debugging