Cannot update/delete Firestore field with "period" in the name - javascript

I'm trying to update/delete a fields in a Firestore document, but the fields that have a "period" in the name seem to be silently failing when trying to update/delete them. The reason I have periods is that I'm using URLs as the keys in the object, I feel this is a semi-common use case.
Example:
First create the document (this works fine)
db.collection("data").doc("temp").set({
helloworld: {
key1: 'foo'
},
hello.world: {
key1: 'bar'
}
})
If you try to delete the element without the period, it works fine.
db.collection("data").doc("temp").update({
helloworld: firebase.firestore.FieldValue.delete()
})
// Value is Deleted
If you try to delete the element with the period, it doesn't do anything.
db.collection("data").doc("temp").update({
hello.world: firebase.firestore.FieldValue.delete()
})
// Nothing Happens!
I've also tried
let u = {}
u['hello.world'] = firebase.firestore.FieldValue.delete()
db.collection("data").doc("temp").update(u)
// Nothing Happens!
Is this a bug? Are periods in field names supported? It seems strange I can create the element but not delete it.

The update operation is reading hello.world as a dot-separated path to a field called word that is nested like this:
{
hello: {
world: "Some value"
}
}
If you have a field with a dot in the name you need to use FieldPath to refer to it literally in an update:
https://firebase.google.com/docs/reference/js/firebase.firestore.FieldPath
So this is what you want:
doc.update(
firebase.firestore.FieldPath("hello.world"),
firebase.firestore.FieldValue.delete());

You need to wrap it in quotes when you use periods in the name on an update or delete like:
db.collection("data").doc("temp").update({
"hello.world": firebase.firestore.FieldValue.delete()
})
or for dynamic names:
[`hello.${world}`]: firebase.firestore.FieldValue.delete()

I found a workaround if you are using dynamic keys and J Livengood's solution doesn't work for you. You can use the "set" method with "merge: true" to selectively set the key with the delete value.
var dynamicKey = "hello.world"
// ES6
db.collection("data").doc("temp").set({
[dynamicKey]: firebase.firestore.FieldValue.delete()
}, { merge: true })
// ES5
var obj = {}
obj[dynamicKey] = firebase.firestore.FieldValue.delete()
db.collection("data").doc("temp").set(obj, { merge: true })

None of the other answers worked for me, the closest one had a new() keyword missing, here is what worked for me
let fpath = new firestore.firestore.FieldPath(`hello.${world}`);
doc.update(
fpath,
firestore.firestore.FieldValue.delete()
);

Related

Accessing the first value of the first key in an object

I am having a little bit of an issue trying to get the value of a certain object. Since this is a bit hard to explain, I'll set up a scenario that follows what I need.
{"Gmail": {"example#example.com": "password1", "anotherexample#example.com": "password2}, ...}
I have an object (as represented above, we will call the object "encrypted"). I can get the value "Gmail" by using Object.keys(encrypted)[i] where i represents the index I'm looking for. The issue I am encountering is, how do I get exaxmple#example.com or password1?
I've been aimlessly wandering around it for a while trying to figure this out, searching for answers, but I can't seem to do so or find any that aren't based on arrays. Any help is great, thank you!
You could use Object.entries
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
This turns objects into arrays of key - value which you can traverse, an example would be something like:
const data = {
"Gmail": { "example#example.com": "password1", "anotherexample#example.com": "password2" },
"Gmail2": { "example#example.com": "password1", "anotherexample#example.com": "password2" },
};
Object.entries(data).forEach(([key, value]) => {
const emailProvider = key;
const emailList = Object.entries(value);
console.log({ mail: emailProvider });
emailList.forEach(([email, password]) => {
console.log({ email, password })
})
});

How to use array.push to add value to an array?

I would like to add a value with array.push to my first element of array [0] to the field yesterday, I don't know very well what the structure is to be able to add this value. try the following way
var cumpleaños = [
{
ayer: "",
},
{
hoy: "12-07-20",
} ,
{
mañana: "12-08-20"
}
];
cumpleaños.push([0].ayer.("12-06-20"))
console.log(cumpleaños[0].ayer)
Thank you very much for the help!
You don't need to push anything since you're not adding a new value, you're just modifying an existing value:
cumpleaños[0].ayer = "12-06-20";
As a side note, your data structure would be much more effectively represented by a single object not inside an array:
const cumpleaños = {
ayer: "12-06-20",
hoy: "12-07-20",
mañana: "12-08-20"
};
You would do assignment to the first element of the array. (If the oder is always the same)
cumpleaños[0].ayer = "12-06-20"

Search for exact value in object (license check)

So I have a file with multiple objects like this:
{
"order":{
"identifier":"B409908375",
"timeCreated":"2018-11-17T18:27:14.423335",
"totalPrice":10.000000000000000000000000000,
"payer_identity":{
"identifier":"K396677386",
"firstName":"Erika",
"lastName":"Mustermann",
"email":"testbuyer#mail.com",
"isEmailVerified":true,
"countryCode3Letter":"DEU"
},
"paymentProviderId":1,
"runtimeLengthDays":-1,
"runtimeOptionCustomIdentifier":"7fbdc628-893c-4499-844c-7a8c7ecaf325",
"productSku":"nd79z8jinqmkmtewfrb5"
},
"license":{
"productSku":"nd79z8jinqmkmtewfrb5",
"issuedToIdentifier":"K396677386",
"validFrom":"2018-11-17T18:27:21.21126",
"validUntil":"9999-01-01T00:00:00",
"isPermanent":true,
"keyIdentifier":"6b3d646f-cb20-4fc5-b520-e53227379407",
"isActive":true
}
}
I am trying to do a license check, so look for the keyIdentifier and see if it matches an input through a form. The input returns {license: "input"} and the result returns {order: {…}, license: {…}}. So yeah I want to check if input value can be found in the result/object.
I don't know if you aim to write your own function to do this from scratch or you want to use built in functions? If you don't have any preferences, you should be able to achieve this like:
const objects = [{object values>}, ...];
function getKeyIdObject(id) {
return objects.find(current => current.license.keyIdentifier === id);
}
getKeyIdObject ('<insert id here>');
This will return the matching object or null if not found. If you want to adjust what you return, you can also add .map(..) to adjust this.
So yeah I want to check if input value can be found in the result/object.
It sounds like you have no idea what is the key name where license number is being stored. But you have a clear name keyIdentifier contained into license breanch, so value can be easily get buy {}.license.keyIdentifier. If you try to do all that on fly you can just put both responses (input / result) into variables and then check if key is correct like this:
const keyFromInput = // here is what you get from input
const result = // here is {order: {…}, license: {…}}
if (keyFromInput === result.license.keyIdentifier) {
//here is what to do if key is correct
} else {
//here is what to do if key is wrong
}
Hope I got you right and you will find this helpful.

Why is the value of a specific key for a doc getting 'undefined' [duplicate]

Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title

How to perform case insensitive lookup in javascript set?

How to perform case-insensitve lookup in javascript's set?
I have a situation where I have a set of allowed strings which doesn't ensure what case they would be in. I need to validate a user input against that set. How can I achieve this?
const countries = new Set();
countries.add("USA");
countries.add("japan");
// returns false, but is there any way I could get
//`my set to ignore case and return true?`
console.log(countries.has("usa"));
console.log(countries.has("USA"));
Just always call .toLowerCase on the string before you add it or before performing a .has check. For sure you can also abstract that into a class (if thats really necessary):
class CaseInsensitiveSet extends Set {
constructor(values) {
super(Array.from(values, it => it.toLowerCase()));
}
add(str) {
return super.add(str.toLowerCase());
}
has(str) {
return super.has(str.toLowerCase());
}
delete(str) {
return super.delete(str.toLowerCase());
}
}
const countries = new CaseInsensitiveSet([
"Usa",
]);
console.log(countries.has("usa")); // true
The short answer is "no". has uses SameValueZero algorithm to seek for a value's existence. See the comparison table here.
If performance is not a concern, you can try two searches, one with uppercased value, and one with lowercased value, and decide whether the value actually exists.
And the better approach would be to always insert the values by converting them to uppercase/lowercase and match accordingly for existence.
Sets check the exact data you have provided. The simplest solution is to save the data in lowercase or UPPERCASE and then search over the set using the .toLoserCase() String method.
Example:
// Save data in lowecase
const set1 = new Set(['test', 'other']);
console.log(set1.has('Test'));
// expected output: false
console.log(set1.has('Other'.toLowerCase()));
// expected output: false
You can add a hasIgnoreCase() prototype on Set.
Set.prototype.hasIgnoreCase = function(str) {
return this.has(str) || this.has(str.toUpperCase());
}
const countries = new Set();
countries.add("USA");
countries.add("japan");
// returns false, but is there any way I could get
//`my set to ignore case and return true?`
console.log(countries.hasIgnoreCase("usa"));
console.log(countries.hasIgnoreCase("USA"));

Categories