Delete Nested Reference in Firestore - javascript

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.

Related

How to get the length of an array from JSON then run a function X number of times

I am trying to get the number of items in an array then run a function that amount of times. Is this possible in JavaScript? I am storing the array in JSON.
This is the code I have set up for when there is only 1 app
if (localStorage.getItem("repo")) {
repo = JSON.parse(localStorage.getItem("repo"));
create_app(repo.app.appName, repo.app.appIcon, repo.app.appId, repo.app.appUrl);
};
This is what the JSON looks like. The array I am trying to access is called "apps".
{
"repoName": "repo_name",
"repoId": "unique_id",
"repoVer": "1",
"apps": [{
"appName": "first app name"
"appId": "app1",
"appUrl": "https://example.com",
"appIcon": "https://example.com/favicon.ico"
},
{
"appName": "second app name",
"appId": "app2",
"appUrl": "https://example.org",
"appIcon": "https://example.org/favicon.ico"
}]
}
repo.apps.length will give you the number of items in apps
Also, since you've already parsed your data into JSON object repo, it now can understand your repo.apps is an array and you can just loop over it directly likes:
repo.apps.forEach((app) => {})
or
for (let app in repo.apps) {}
etc
Hope this help

Keep order of objects inside a JSON String after they are parsed

I receive the following JSON string from an API function.
"Inbound": {
"callRelatedFields": ["ANI",
"DNIS"],
"objects": {
"Contact": [{
"displayName": "Name",
"apiName": "Name"
},
{
"displayName": "Email",
"apiName": "Email"
}],
"Account": [{
"displayName": "Account Name",
"apiName": "Name"
},
{
"displayName": "Phone",
"apiName": "Phone"
},
{
"displayName": "Fax",
"apiName": "Fax"
}],
"cnx__Phone__c": [{
"displayName": "Phone Name",
"apiName": "Name"
},
{
"displayName": "Phone Number Line 1",
"apiName": "cnx__Phone_Number_Line_1__c"
},
{
"displayName": "Phone Number Line 2",
"apiName": "cnx__Phone_Number_Line_2__c"
},
{
"displayName": "Type",
"apiName": "cnx__Type__c"
},
{
"displayName": "Location",
"apiName": "cnx__Location__c"
},
{
"displayName": "Call Manager",
"apiName": "cnx__Call_Manager__c"
},
{
"displayName": "Mac Address",
"apiName": "cnx__Mac_Address__c"
}]
},
"screenPopSettings": {
"screenPopsOpenWithin": "ExistingWindow",
"SingleMatch": {
"screenPopType": "PopToEntity"
},
"NoMatch": {
"screenPopType": "DoNotPop"
},
"MultipleMatches": {
"screenPopType": "DoNotPop"
}
}
}
The order of the objects inside "objects" is important!
But when i parse this JSON string with JSON.parse, the order of those objects is lost.
Is there any good way to keep the order of those objects after they are parsed.
I tried to manipulate the string and convert the whole "objects" into an array, but this turned out to become way too complicated and hacky.
I have a suspicion that the thing that makes you think the keys have changed order is that Chrome devtools show objects with their keys sorted in alphabetical order. Whereas if you use Object.keys() or the equivalent JS to manually iterate through the keys, you will find they come out in the order they were defined in the JSON string.
Here is the equivalent JS for Object.keys():
function objectKeys(obj) {
var keys = [];
if (!obj) return keys;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
}
When I call this with the objects part of the parsed object I get the following array:
["Contact", "Account", "cnx__Phone__c"]
Unfortunately object properties are unordered in JavaScript so you shouldn't rely on being able to iterate over them in a particular sequence.
I would suggest accessing the properties by name in the order you need them, rather than just iterating over the list.
As per the JSON standard, an object is unordered. So if you care about the order "Contact", "Account", "cnx__Phone__c", put them in an array ([]).
Maybe it's enough to put the property names themselves in an array next to the .objects themselves, so that you still can access them by their names. Many structures are valid solutions.
This solution works only if the properties and the data does not contain one of these characters: {, } and :.
Maybe you replace the curly brackets to square brackets and ": to #",. After that, you can the JSON string parse and get all objects replaced by arrays. The reading is: first value is the property (marked with # at the end) and the second value is the value.
The replacement machanism shuld be improved, in particular the replacement of ":, which can sometimes be wrong, and the search of the curly brackets.
var json = '{"Inbound":{"callRelatedFields":["ANI","DNIS"],"objects":{"Contact":[{"displayName":"Name","apiName":"Name"},{"displayName":"Email","apiName":"Email"}],"Account":[{"displayName":"Account Name","apiName":"Name"},{"displayName":"Phone","apiName":"Phone"},{"displayName":"Fax","apiName":"Fax"}],"cnx__Phone__c":[{"displayName":"Phone Name","apiName":"Name"},{"displayName":"Phone Number Line 1","apiName":"cnx__Phone_Number_Line_1__c"},{"displayName":"Phone Number Line 2","apiName":"cnx__Phone_Number_Line_2__c"},{"displayName":"Type","apiName":"cnx__Type__c"},{"displayName":"Location","apiName":"cnx__Location__c"},{"displayName":"Call Manager","apiName":"cnx__Call_Manager__c"},{"displayName":"Mac Address","apiName":"cnx__Mac_Address__c"}]},"screenPopSettings":{"screenPopsOpenWithin":"ExistingWindow","SingleMatch":{"screenPopType":"PopToEntity"},"NoMatch":{"screenPopType":"DoNotPop"},"MultipleMatches":{"screenPopType":"DoNotPop"}}}}';
json = json.replace(/{/g, '[').replace(/}/g, ']').replace(/"\:/g, '#",');
json = JSON.parse(json);
document.write('<pre>' + JSON.stringify(json, 0, 4) + '</pre>');
#GregL is right the JSON parsed came in alphabetic or in case of a number in ascending order and to keep the order you'll need an incremented number logic like:
var position_in_array = 0
var name = 'screenPopSettings'
object[`${position_in_array}${name}`] = value
position_in_array += 1
The parseJson returns data in object form and object doesn't has index. So we should define custom index of data array, if we want to keep the array index.
Example:
$arr[0] = array(
'Contact'=>array(
'key1'=>'val',
)
);
$arr[1] = array(
'Account'=>array(
'key1'=>'val',
)
);
It will produce the output as per the array index originally defined before parseJson function call.

Access anonymous array using NgRepeat

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

MongoDb: How to get a field (sub document) from a document?

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"
}
}
}
}

Indexing page elements to JSON object? Or jQuery selector it every time?

I have a DOM:
<body>
<div>
<h1 id="header1">home1</h1>
<a id="link1">link1</a>
<p id="para1">things1</p>
</div>
<span>
<h1 id="header2">home2</h1>
<a id="link2">link2</a>
<p id="para2">para2</p>
</span>
</body>
I want to index this to JSON like:
{
"body": {
"div": [
{
"h1": "header1",
"a": "link1",
"p": "para1"
}
],
"span": [
{
"h1": "header2",
"a": "link2",
"p": "para2"
}
]
}
}
I've tried this:
function indexELEMS()
{
listy = $("*[id]").map(function(){
outy = this.tagName+":"+this.id;
return outy;
}).get();
DOMobj = $.extend({}, listy);
console.log(DOMobj);
}
But I get something like this:
0:"h1:home"
1:"a:link1"
2:"p:things1"
Understandable, I've just told the function to do that.
But if I try it on just the h1's like this:
function indexELEMS()
{
outy = {};
listy = $("h1[id]").map(function(){
outy.h1s = this.tagName+":"+this.id;
return outy;
}).get();
DOMobj = $.extend({}, listy);
console.log(DOMobj);
}
It will overwrite the outy.h1s with the last one, how do I add it to the (JSON)object?
Or better yet, how do I get the whole document element structure output in nice JSON form?
I could do: $('document > body > div > h1').attr('id') every time, but that's pretty inefficient and resource intensive, I want to cache all the elements and then read them out, and when they change, (maybe I'll watch the object with .watch), or is added in the object, create an element in the appropriate position.
Or is there a better way get an overview of which elements have IDs (and check if that ID is duplicate or not), some native function?
I'm also afraid that the JSON Object won't allow multiple div: entries?
Or should I just go for the jQuery selector completely and stop whining about efficiency?
(edit: Ok, the multiple div part is maybe not possible, or can't I access the second one with .div[1] ?)
So, my main question would be, if you take the indexELEMS function, how can I get an object that is accesibly by:
DOMobj.body.div.h1
So I can do an If == on it, or cycle through it with div.[index] in a $.each loop
I would think, cycling through DOMobj.body[index] instead of $(''), oh wait, can I actually access the object created from $('')? like .body? It can't be that easy..
Your structure is internally inconsistent: <body> has an object child, but all its children are array-with-a-single-object. Should this map to any page DOM or just some custom one of your own?
If you want any DOM to be able to be represented as JSON, is what you seek rather something like this?
[ { "tag": "body"
, "children":
[ { "tag": "div"
, "children":
[ { "tag": "h1"
, "id": "header1"
}
, { "tag": "a"
, "id": "link1"
}
, { "tag": "p"
, "id": "para1"
}
]
}
, { "tag": "span"
, "children":
[ { "tag": "h1"
, "id": "header2"
}
, { "tag": "a"
, "id": "link2"
}
, { "tag": "p"
, "id": "para2"
}
]
}
]
}
]
If you only use case for this JSON object to answer queries about the DOM, and do some iteration, you're probably best off using $('your.selector.here').each and similar code performing the tests you want.
If you want to index manually all the way down through the DOM (find the <body>'s second <div> child's fifth <a> element, and tell me if its id attribute is "link5"), you want XPath:
document.evaluate('//body/div[2]/a[5]/#id = "link5"', document).booleanValue

Categories

Resources