I have a multi-level array containing some objects at its deepest level.
[
[
[
"FUND",
{
"totassets":10.9,
"totdate":"2015-03-23",
"expratiogross":1.35,
"exprationet":1.08
}
],
[
"DAILY",
{
"navdate":"2015-03-23",
"nav":10.05,
"chgamt":0,
"chgpct":0,
"pop":10.05,
"ytdreturn":2.03,
"curr7dayyield":0,
"eff7dayyield":0,
"unsub7dayyield":0,
"30dayyield":0,
"30dayloadyield":0
}
]
]
]
I would like to use ngRepeat to display all the items in "FUND" or "DAILY" but I'm unsure how to access objects this deep without names for each of the arrays above.
Sorry if this is a basic question but I wasn't able to find an answer elsewhere.
You'll want to get the first element of your two outer arrays.
$scope.obj = [
[
[
"FUND",
{
"totassets":10.9,
"totdate":"2015-03-23",
"expratiogross":1.35,
"exprationet":1.08
}
],
[
"DAILY",
{
"navdate":"2015-03-23",
"nav":10.05,
"chgamt":0,
"chgpct":0,
"pop":10.05,
"ytdreturn":2.03,
"curr7dayyield":0,
"eff7dayyield":0,
"unsub7dayyield":0,
"30dayyield":0,
"30dayloadyield":0
}
]
]
]
<ng-repeat el in obj[0][0]>
<span>totassets: {{el[0].FUND.totalAssets}}</span>
<span>navdate: {{el[0].DAILY.navdate}}</span>
</ng-repeat>
An issue you have with the array is that even when you ignore the outer arrays, you're still left with two individual arrays a la:
[
"FUND",
{
"totassets":10.9,
"totdate":"2015-03-23",
"expratiogross":1.35,
"exprationet":1.08
}
],
And:
[
"DAILY",
{
"navdate":"2015-03-23",
"nav":10.05,
"chgamt":0,
"chgpct":0,
"pop":10.05,
"ytdreturn":2.03,
"curr7dayyield":0,
"eff7dayyield":0,
"unsub7dayyield":0,
"30dayyield":0,
"30dayloadyield":0
}
]
So you will need two ngRepeat blocks to achieve what I assume you want to achieve as well as going one level deeper to actually access the values you want.
Here's a quick plnkr to demonstrate what I mean: http://plnkr.co/edit/ArCh8q8w2JoXsg107XwP?p=preview
Related
I am trying to delete a 2 levels deep nested Reference in Firestore. My Schema looks like this:
In Code it looks like this:
{
"folder": "bla",
"title": "myTitle",
"children": [
{
"ref": "firstReference"
},
{
"ref": "secondReference"
},
{
"title": "Subcollection Title",
"children": [
{
"ref": "thirdReference"
},
{
"ref": "forthReference"
}
]
}
]
}
Now i am searching a way to remove the third or forth Reference from the second children array.
To remove an item from the first children array is use this code:
docRef.update({children: firebase.firestore.FieldValue.arrayRemove(folder.children[index])
But this solution works only for the top level ;(
Does somebody know how to remove deeper Nested elements?
I tried:
docRef.update({[`children[${index}].children`]: firebase.firestore.FieldValue.arrayRemove(
folder.children[index].children[secondIndex])});
But it throws an Error (Paths must not contain '~', '*', '/', '[', or ']')
Thanks for your help ;)
Firestore does not support modifying array items by index. FieldValue.arrayRemove only works if you pass the exact contents of the data to remove from an array field. If you only know the index, then what you'll have to do is read the document, modify the array in memory, then write the new document contents back.
What I want to do is intersect two objects.
I want to compare the objects, and if they have same values on same keys, just add them to another object.
obj1 = { "Projects": [ "test" ], "Companies": [ "facebook", "google", "yahoo" ], "Locations": [ "LA", "NY" ], "Interests": [] }
obj2 = { "Projects": [ "test" ], "Companies": [ "netflix", "skype", "facebook" ], "Locations": [ "sttugart", "torino", "LA" ], "Interests": [] }
The result will be:
obj3 = { "Projects": [ "test" ], "Companies": [ "facebook" ], "Locations": [ "LA" ], "Interests": [] }
What i tried is something like this:
intersect(obj1, obj2)
for(let key of obj1)
if(obj2[key] == obj1[key]) obj3[key] = obj2[key];
And yes, i did checked SO for other solutions, i had no result.
EDIT
My attempt dind't probably work because my object is not an array type or a string type
This isnt really a problem just for angular 2 but more javascript in itself. No angular functions will probably help you here
Using lodash or underscore.js might prove to be more productive and useful
However if you insist that you need to do this in your own way. there are two cases
One is that you already know how many objects you would be comparing
Two is that you don't know how many objects you would be comparing
For case one it would a simple for loop with && cases for logical comparisons
For case two i would suggest you first push all your objects that need to be compared into an array and iterate through there.
Use lodash
Here you will find a good documentation:
https://lodash.com/docs/4.16.2#intersection
We often use it with good experience
I have array of users who have a property array 'rights' and I want to filter out the users who have specific rights. I would like to filter by an array so if I wanted all the users with full rights ['full'] or users with both full and edit ['full','edit']. I am fairly new to using lodash and I think I can chain some together but I am not sure if this is there are more efficient ways of doing it.
Here is my plunker: http://plnkr.co/edit/5PCvaDJaXF4uxRowVBlK?p=preview
Result ['full'] :
[{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
}]
Result ['full','edit']:
[{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
},
{
"name": "EditRights Company1",
"rights": [
"edit"
]
}]
Code:
var users = [
{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
},
{
"name": "ApproveRights Company1",
"rights": [
"approve","review"
]
},
{
"name": "EditRights Company1",
"rights": [
"edit"
]
},
{
"name": "ReviewRights Company1",
"rights": [
"review"
]
},
{
"name": "NoRights Company1",
"rights": [
"none"
]
}
];
var tUsers = [];
var filterRights = ['full','edit'];
_.forEach(users, function(user) {
if (_.intersection(user.rights, filterRights).length > 0) {
tUsers.push(user);
}
}) ;
//console.log('users', JSON.stringify(users, null, 2));
console.log('tUsers', JSON.stringify(tUsers, null, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
From the docs
_.filter(collection, predicate, thisArg);
Arguments
collection (Array|Object|string): The collection to iterate over.
[predicate=_.identity] (Function|Object|string): The function invoked per iteration.
[thisArg] (*): The this binding of predicate.
Chaining is great when you want to connect different processing steps.
If your problem statement was to
filter by rights
sort by oldest person
take 10
Then chaining would make a lot of sense.
This problem seems to be mostly custom logic on filtering.
var users = [/* Your user data here */];
function filterByRights (users, rights) {
return _.filter(users, function (user) {
return _.any(user.rights, function (right) {
return _.contains(rights, right);
});
});
}
filterByRights(users, ['full', 'edit']); // [/*Users with full or edit rights*/]
I think my example is good becuase it doesn't depend on conditional logic. It uses lodash defined methods like any and contains
Performance concerns
I want to expand on what performance concerns you have. Here are a couple of points.
Your question code is maintaining its own mechanism for filtering out users. While it is a perfectly good solution you should opt into letting the guys who maintain lodash handle this logic. They have probably spent a lot of time optimizing how to create another array from an original one.
_.any is more efficient than _.intersection. _.intersection needs to process every element to know what the intersection is. _.any stops when it hits the first element which passes the predicate otherwise it checks each of them. This point is minor since there are a small number of "rights"
The example I've given is probably more "lodash standard". You typically can do data transformations completely with lodash defined methods and trivial predicates.
Here is an update to #t3dodson 's answer. You should now use the following snippet if using current (4.17.4) Lodash version:
function filterByRights (users, rights) {
return _.filter(users, function (user) {
return _.some(user.rights, function (right) {
return _.includes(rights, right);
});
});
}
From the Changelog:
Removed _.contains in favor of _.includes
Removed _.any in favor of _.some
I think you were on the right path with intersection() (I've never seen any performance issues with this function). Here's how I would compose an iteratee using flow():
_.filter(users, _.flow(
_.property('rights'),
_.partial(_.intersection, filterRights),
_.size
));
The property() function gets the rights property, and passes it to intersection(). We've already partially-applied the filterRights array. Lastly, the size() function is necessary to pass a thruthy/falesy value to filter().
Meteor application, where I have a mongo collection that has a tags field.
[{name: "ABC", tags: {"#Movie", "#free", "!R"}},
{name: "DEF", tags: {"#Movie", "!PG"}},
{name: "GHI", tags: {"#Sports", "#free"}}]
On my UI, there are three groups of checkboxes that are populated on the fly, based on the first letter of the tag name.
filter group 1: [ ]Movie [ ] Sports
filter group 2: [ ]free
filter group 3: [ ]PG [ ]R
The filter logic is the following:
If filter group is empty then do not filter by that filter group
If any checkbox from a filter group is checked, then apply that filter
$and should be applied between filter groups (if movies and R checked, then only documents that have tags named "!Movie" and "#free" should be selected
I am struggling to build a mongo criteria parameters that follows the above logic. My code currently looks like spaghetti with lots of nested ifs (in pseudo code)
if (filter_group1 is empty) then if (filter_group2 is empty) then mongo_criteria= {_id: $in: $("input:checked", ".filtergroup1").map(function() {return this.value})}
What would be the right way of doing this?
Firstly, I'm sure you mean that "tags" is actually an array since otherwise the structure would be invalid:
{ "name": "ABC", "tags": ["#Movie", "#free", "!R"]},
{ "name": "DEF", "tags": ["#Movie", "!PG"]},
{ "name": "GHI", "tags": ["#Sports", "#free"]}
It's a novel idea to store "tags" data this way, but it does seem that your program logic to construct a query needs to be aware that there are at least "three" possible conditions that need to be considered in an $and combination.
In the simplest form where you only allowed one selection per filter group then you could get away with coming out to this with the $all operator. Just in simple MongoDB shell notation for brevity:
db.collection.find({ "tags": { "$all": [ "#Movie", "!R" ] } })
The problem there is that if you wanted multiple selections on a group, say the rating for example, then this would fail to get a result:
db.collection.find({ "tags": { "$all": [ "#Movie", "!R", "!PG" ] } })
No item in fact contains both those rating values so this would not be valid. So you would rather do this:
db.collection.find({ "$and": [
{ "tags": { "$in": [ "#Movie" ] } },
{ "tags": { "$in": [ "!R", "!PG" ] } }
])
That would correctly match all Movies with ratings tags for "R" and "PG". Extending this for another group is basically pushing another array item to the $and expression:
db.collection.find({ "$and": [
{ "tags": { "$in": [ "#Movie" ] } },
{ "tags": { "$in": [ "!R", "!PG" ] } },
{ "tags": { "$in": [ "#free" ] }
])
Getting only the document which contains each of those "types" of filters to the matching value, so the "PG" movie is not free and "Sports" was filtered out by not adding to the selection.
The basics of constructing the query is working with an array of selection options for $in in each filter group. Of course then you only append to the $and array when there is a selection present in your filter group.
So start with a base $and like this:
var query = { "$and":[{}] };
And then add in each of the checked options in each filter group to its own in:
var inner = { "tags": { "$in": [] } };
inner.tags["$in"].push( item );
And then append to the base query:
query["$and"].push( inner );
Rinse and repeat for each item. And this is perfectly valid since the base query will just select everything unfiltered, and this is also valid without constructing additional logic:
db.collection.find({ "$and": [
{ },
{ "tags": { "$in": [ "#Movie" ] } },
{ "tags": { "$in": [ "!R", "!PG" ] } },
{ "tags": { "$in": [ "#free" ] }
])
So it really comes down to contruction of the query as MongoDB understands it. This is really just simple JavaScript array manipulation in building the data structure. Which is all MongoDB queries really are.
Consider this example collection:
{
"_id:"0,
"firstname":"Tom",
"children" : {
"childA":{
"toys":{
'toy 1':'batman',
'toy 2':'car',
'toy 3':'train',
}
"movies": {
'movie 1': "Ironman"
'movie 2': "Deathwish"
}
},
"childB":{
"toys":{
'toy 1':'doll',
'toy 2':'bike',
'toy 3':'xbox',
}
"movies": {
'movie 1': "Frozen"
'movie 2': "Barbie"
}
}
}
}
Now I would like to retrieve ONLY the movies from a particular document.
I have tried something like this:
movies = users.find_one({'_id': 0}, {'_id': 0, 'children.ChildA.movies': 1})
However, I get the whole field structure from 'children' down to 'movies' and it's content. How do I just do a query and retrieve only the content of 'movies'?
To be specific I want to end up with this:
{
'movie 1': "Frozen"
'movie 2': "Barbie"
}
The problem here is your current data structure is not really great for querying. This is mostly because you are using "keys" to actually represent "data points", and while it might initially seem to be a logical idea it is actually a very bad practice.
So rather than do something like assign "childA" and "childB" as keys of an object or "sub-document", you are better off assigning these are "values" to a generic key name in a structure like this:
{
"_id:"0,
"firstname":"Tom",
"children" : [
{
"name": "childA",
"toys": [
"batman",
"car",
"train"
],
"movies": [
"Ironman"
"Deathwish"
]
},
{
"name": "childB",
"toys": [
"doll",
"bike",
"xbox",
],
"movies": [
"Frozen",
"Barbie"
]
}
]
}
Not the best as there are nested arrays, which can be a potential problem but there are workarounds to this as well ( but later ), but the main point here is this is a lot better than defining the data in "keys". And the main problem with "keys" that are not consistently named is that MongoDB does not generally allow any way to "wildcard" these names, so you are stuck with naming and "absolute path" in order to access elements as in:
children -> childA -> toys
children -> childB -> toys
And that in a nutshell is bad, and compared to this:
"children.toys"
From the sample prepared above, then I would say that is a whole lot better approach to organizing your data.
Even so, just getting back something such as a "unique list of movies" is out of scope for standard .find() type queries in MongoDB. This actually requires something more of "document manipulation" and is well supported in the aggregation framework for MongoDB. This has extensive capabilities for manipulation that is not present in the query methods, and as a per document response with the above structure then you can do this:
db.collection.aggregate([
# De-normalize the array content first
{ "$unwind": "$children" },
# De-normalize the content from the inner array as well
{ "$unwind": "$children.movies" },
# Group back, well optionally, but just the "movies" per document
{ "$group": {
"_id": "$_id",
"movies": { "$addToSet": "$children.movies" }
}}
])
So now the "list" response in the document only contains the "unique" movies, which corresponds more to what you are asking. Alternately you could just $push instead and make a "non-unique" list. But stupidly that is actually the same as this:
db.collection.find({},{ "_id": False, "children.movies": True })
As a "collection wide" concept, then you could simplify this a lot by simply using the .distinct() method. Which basically forms a list of "distinct" keys based on the input you provide. This playes with arrays really well:
db.collection.distinct("children.toys")
And that is essentially a collection wide analysis of all the "distinct" occurrences for each"toys" value in the collection, and returned as a simple "array".
But as for you existing structure, it deserves a solution to explain, but you really must understand that the explanation is horrible. The problem here is that the "native" and optimized methods available to general queries and aggregation methods are not available at all and the only option available is JavaScript based processing. Which even though a little better through "v8" engine integration, is still really a complete slouch when compared side by side with native code methods.
So from the "original" form that you have, ( JavaScript form, functions have to be so easy to translate") :
db.collection.mapReduce(
// Mapper
function() {
var id this._id;
children = this.children;
Object.keys(children).forEach(function(child) {
Object.keys(child).forEach(function(childKey) {
Object.keys(childKey).forEach(function(toy) {
emit(
id, { "toys": [children[childkey]["toys"][toy]] }
);
});
});
});
},
// Reducer
function(key,values) {
var output = { "toys": [] };
values.forEach(function(value) {
value.toys.forEach(function(toy) {
if ( ouput.toys.indexOf( toy ) == -1 )
output.toys.push( toy );
});
});
},
{
"out": { "inline": 1 }
}
)
So JavaScript evaluation is the "horrible" approach as this is much slower in execution, and you see the "traversing" code that needs to be implemented. Bad news for performance, so don't do it. Change the structure instead.
As a final part, you could model this differently to avoid the "nested array" concept. And understand that the only real problem with a "nested array" is that "updating" a nested element is really impossible without reading in the whole document and modifying it.
So $push and $pull methods work fine. But using a "positional" $ operator just does not work as the "outer" array index is always the "first" matched element. So if this really was a problem for you then you could do something like this, for example:
{
"_id:"0,
"firstname":"Tom",
"childtoys" : [
{
"name": "childA",
"toy": "batman"
}.
{
"name": "childA",
"toy": "car"
},
{
"name": "childA",
"toy": "train"
},
{
"name": "childB",
"toy": "doll"
},
{
"name": "childB",
"toy": "bike"
},
{
"name": "childB",
"toy": "xbox"
}
],
"childMovies": [
{
"name": "childA"
"movie": "Ironman"
},
{
"name": "childA",
"movie": "Deathwish"
},
{
"name": "childB",
"movie": "Frozen"
},
{
"name": "childB",
"movie": "Barbie"
}
]
}
That would be one way to avoid the problem with nested updates if you did indeed need to "update" items on a regular basis rather than just $push and $pull items to the "toys" and "movies" arrays.
But the overall message here is to design your data around the access patterns you actually use. MongoDB does generally not like things with a "strict path" in the terms of being able to query or otherwise flexibly issue updates.
Projections in MongoDB make use of '1' and '0' , not 'True'/'False'.
Moreover ensure that the fields are specified in the right cases(uppercase/lowercase)
The query should be as below:
db.users.findOne({'_id': 0}, {'_id': 0, 'children.childA.movies': 1})
Which will result in :
{
"children" : {
"childA" : {
"movies" : {
"movie 1" : "Ironman",
"movie 2" : "Deathwish"
}
}
}
}