Cloud Functions: Values not read in Map - javascript

Why am I unable to read the following variables in a nested map?
for (const key in doc.data().category) {
const location = doc.data().location; // declared but it's value is never read"
const mainCategory = doc.data().category; // declared but it's value is never read"
const subCategory = doc.data().category[key]; // declared but it's value is never read"
categoryCount.doc('categoryCount')
.set({ location: { mainCategory: { subCategory: "test" } } },
{ merge: true })
.catch((error) => console.log(error));
Console logs to clarify:
console.log(location); // "New York"
const map = { location: { mainCategory: { subCategory: true } } };
console.log(map); // "location": {"mainCategory": {"subCategory": true}}

If you want to use the value of a variable as the name of a property, you have to tell JavaScript that you want to insert that value (as opposed to just naming the key):
{ [location]: { mainCategory: { subCategory: "test" } } }
Notice the square brackets around location.
See also: Square Brackets Javascript Object Key

I think you may be misunderstanding how JavaScript objects work. Imagine you have three variables called:
var A = 'X';
var B = 'Y';
var C = 'Z';
when you code:
{
A: {
B: {
C: "test"
}
}
}
You do not end up with an object of value:
{
X: {
Y: {
Z: "test"
}
}
}
If that is what you want, consider:
var O1 = {};
O1[C] = "test";
var O2 = {};
O2[B] = O1;
var O3 = {};
O3[A] = O2;
// O3 is the top level object

Related

Unexpected result when attempting to add an object to an array

Just a bit confused as to why the following is occurring:
let groupdetails = {
groupName: "",
};
const groupsArray = [];
groupdetails.groupName = 'A'
groupsArray.push(groupdetails)
groupdetails.groupName = 'B'
groupsArray.push(groupdetails)
console.log(groupsArray)
The result I am getting is:
[ { groupName: "B" }, { "groupName": "B" } ]
The result I was expecting was:
[ { groupName: "A" }, { "groupName": "B" } ]
Unsure what I am doing wrong?
You should create a different reference each time you push an element
let groupdetails = {
groupName: "",
};
const groupsArray = [];
groupdetails.groupName = 'A'
groupsArray.push(groupdetails)
groupdetails = {...groupdetails}
groupdetails.groupName = 'B'
groupsArray.push(groupdetails)
console.log(groupsArray)
You are facing issue with Shallow Copy and Deep Copy..
In your case eventhough you were updating the value groupdetails.groupName = 'B', this referes to the same node as the one you pushed initially. You have to creat a new node with spread operater or some thing and update its value and push it again.
In order to achieve your requirement you have to follow something like below mentioned.
let groupdetails = {
groupName: "",
};
const groupsArray = [];
groupdetails.groupName = 'A'
groupsArray.push(groupdetails);
const newNode = { ...groupdetails };
newNode.groupName = 'B';
groupsArray.push(newNode);
console.log(groupsArray);
Remember Arrays, Objects are copied by references, not just by values
a = 'John';
b = a;
console.log(a === b);
Here values are copied to another variable.
obj = {name: 'John'};
newObj = obj;
newObj.name = 'Kate';
console.log(newObj); // {name: 'Kate'}
console.log(obj) // {name: 'Kate'}
Here the reference is passed to the other object. So once an object is modified it will be reflected on the other object.
In your case, this will work,
const groupsArray = [];
function addNew(value) {
return { groupName: value}
}
groupsArray.push(addNew('A'));
groupsArray.push(addNew('B'));
console.log(groupsArray);

Multi level dynamic key setting?

How do you set a multi-level deep update on an object with a dynamic key in javascript? When i try a typical dynamic update, it adds another key/value pair instead of updating the correct parameter.
let home = {
street: {
house: {
room: {
window: true
}
}
}
}
let update = {
key: "house.room.window",
value: "false"
}
home.street[update.key] = update.value;
console.log(home);
expected:
home = {
street:{
house: {
room: {
window: false
}
}
}
}
instead i get:
home = {
street:{
house: {
room: {
window: true
}
}
"house.room.window": false
}
}
Try like below. Explanation is in comments.
let home = {
street: {
house: {
room: {
window: true
}
}
}
}
let update = {
key: "house.room.window",
value: "false"
}
// select object to get updated
let obj = home.street;
// get array of nested keys
let nestedKeys = update.key.split('.');
// get object from nested keys until last key
// used slice(0, -1) so it will iterate through all key except last one
nestedKeys.slice(0, -1).forEach(k => obj = obj[k]);
// use object with last key and update value
obj[nestedKeys[nestedKeys.length - 1]] = update.value
// log object
console.log(home);
If you don't mind using a library, you can try lodash's update.
_.update(home, update.key, () => update.value);

how to create nested object from an array with same key (javascript)

arr = ["sadik", "arif", "rahman"]
I want to create a nested object with the same key but different value like:
{
subcategory: {
name: sadik
subcategory: {
name: arif
subcategory: {
name: rahman
}
}
}
}
my code:
let arr = ['sadik', 'arif', 'babor']
let obj = {}
arr.forEach((elem) => {
let a = {}
a["subcategory"] = {name:elem}
Object.assign(obj, a)
})
i get only last value:
{
subcategory: {
name:"babor"
}
}
Your code did not work because you were replacing your "subcategory" key value in each iteration. You have change the reference object to the next nested level each time to get the expected output, see the working snippet below:
const arr = ['level-1', 'level-2', 'level-3']
const obj = {}
let refObj = obj;
arr.forEach( ele => {
refObj = refObj['subcategory'] || refObj;
refObj['subcategory'] = { 'name': ele};
})
console.log('output', obj);

Update fields in nested objects in Typescript / Javascript

In Firestore you can update fields in nested objects by a dot notation (https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0#update_fields_in_nested_objects). I wonder how to make that work in Typescript / Javascript.
For example the following object:
const user = {
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: true
}
},
token: {
custom: 'safghhattgaggsa',
public: 'fsavvsadgga'
}
}
How can I update this object with the following changes:
details.email.verified = false;
token.custom = 'kka';
I already found that Lodash has a set function:
_.set(user, 'details.email.verified', false);
Disadvantage: I have to do this for every change. Is their already a method to update the object with an object (like firestore did)?
const newUser = ANYFUNCTION(user, {
'details.email.verified': false,
'token.custom' = 'kka'
});
// OUTPUT for newUser would be
{
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: false
}
},
token: {
custom: 'kka',
public: 'fsavvsadgga'
}
}
Does anyone know an good solution for this? I already found more solutions if I only want to change one field (Dynamically set property of nested object), but no solution for more than one field with one method
I think you are stuck with using a function but you could write it yourself. No need for a lib:
function set(obj, path, value) {
let parts = path.split(".");
let last = parts.pop();
let lastObj = parts.reduce((acc, cur) => acc[cur], obj);
lastObj[last] = value;
}
set(user, 'details.email.verified', false);
if what you want to do is merge 2 objects then it is a bit trickier:
function forEach(target, fn) {
const keys = Object.keys(target);
let i = -1;
while (++i < keys.length) {
fn(target[keys[i]], keys[i]);
}
}
function setValues(obj, src) {
forEach(src, (value, key) => {
if (value !== null && typeof (value) === "object") {
setValues(obj[key], value);
} else {
obj[key] = value;
}
});
}
let obj1 = {foo: {bar: 1, boo: {zot: null}}};
let obj2 = {foo: {baz: 3, boo: {zot: 5}}};
setValues(obj1, obj2);
console.log(JSON.stringify(obj1));
One solution in combination with lodash _.set method could be:
function setObject(obj, paths) {
for (const p of Object.keys(paths)) {
obj = _.set(obj, p, paths[p]);
}
return obj;
}

Multiple key names, same pair value

I'm trying to setup an object literal in a JavaScript script that has a key with multiple names. referring to the same object value i.e. something like these that I have already tried:
var holidays: {
"thanksgiving day", "thanksgiving", "t-day": {
someValue : "foo"
}
}
var holidays: {
["thanksgiving day", "thanksgiving", "t-day"]: {
someValue : "foo"
}
}
Is there a way I can accomplish this?
Another approach is to do some postprocessing
function expand(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i],
subkeys = key.split(/,\s?/),
target = obj[key];
delete obj[key];
subkeys.forEach(function(key) { obj[key] = target; })
}
return obj;
}
var holidays = expand({
"thanksgiving day, thanksgiving, t-day": {
someValue : "foo"
}
});
JSON does not offer such a feature, nor do Javascript object literals.
You might be able to make do with something like this:
holidays = {
thanksgiving: {foo: 'foo'},
groundhogDay: {foo: 'bar'},
aliases: {
'thanksgiving day': 'thanksgiving',
't-day': 'thanksgiving',
'Bill Murrays nightmare': 'groundhogDay'
}
}
and then you can check
holidays[name] || holidays[holidays.aliases[name]]
for your data.
It's not a wonderful solution. But it wouldn't be too difficult to write a little function that created this sort of object out of a representation like:
[
{
names: ['thanksgiving', 'thanksgiving day', 't-day'],
obj: {foo: 'foo'}
},
{
names: ['groundhogDay', 'Bill Murrays nightmare'],
obj: {foo: 'bar'}
},
]
if that would be easier to maintain.
Another solution, if you can afford RegExp execution, and ES6 Proxy:
let align = new Proxy({
'start|top|left': -1,
'middle|center': 0,
'end|bottom|right': 1,
}, {
get: function(target, property, receiver) {
for (let k in target)
if (new RegExp(k).test(property))
return target[k]
return null
}
})
align.start // -1
align.top // -1
align.left // -1
align.middle // 0
align.center // 0
align.end // 1
align.bottom // 1
align.right // 1
See MDN Proxy
2021 EDIT:
Another (cleaner?) solution using reduce & defineProperty :
const myDict = [
// list of pairs [value, keys],
// note that a key should appear only once
[-1, ['start', 'left', 'top']],
[0, ['center', 'middle']],
[1, ['end', 'right', 'bottom']],
].reduce((obj, [value, keys]) => {
for (const key of keys) {
Object.defineProperty(obj, key, { value })
}
return obj
}, {})
I guess you could do something like this:
var holidays = {
'thanksgiving day': {
foo: 'foo'
}
};
holidays.thanksgiving = holidays['t-day'] = holidays['thanksgiving day'];
If you see yourself doing this often or you have more values consider this pattern:
'thanksgiving, t-day, thanks, thank, thank u'.split(',').forEach(function(key) {
holidays[key] = holidays['thanksgiving day'];
});
A better approach would be to process your data beforehand instead of adding duplicates.
That should work as expected:
function getItem(_key) {
items = [{
item: 'a',
keys: ['xyz','foo']
},{
item: 'b',
keys: ['xwt','bar']
}];
_filtered = items.filter(function(item) {
return item.keys.indexOf(_key) != -1
}).map(function(item) {
return item.item;
});
return !!_filtered.length ? _filtered[0] : false;
}
With ES6 you could do it like this, but it's not ideal:
const holidays = {
"single": {
singleValue: "foo",
},
...([
"thanksgiving day", "thanksgiving", "t-day",
].reduce((a, v) => ({...a, [v]: {
someValue: "foo",
}}), {})),
"other": {
otherValue: "foo",
},
};
I still think the cleanest solution is probably:
let holidays = {
"t-day": {
someValue: "foo",
},
};
holidays["thanksgiving"] = holidays["t-day"];
holidays["thanksgiving day"] = holidays["t-day"];
Now this may be overkill for you, but here's a generic function that will create an object with "multiple keys." What it actually does is have one real property with the actual value, and then defines getters and setters to forward operations from the virtual keys to the actual property.
function multiKey(keyGroups) {
let obj = {};
let props = {};
for (let keyGroup of keyGroups) {
let masterKey = keyGroup[0];
let prop = {
configurable: true,
enumerable: false,
get() {
return obj[masterKey];
},
set(value) {
obj[masterKey] = value;
}
};
obj[masterKey] = undefined;
for (let i = 1; i < keyGroup.length; ++i) {
if (keyGroup.hasOwnProperty(i)) {
props[keyGroup[i]] = prop;
}
}
}
return Object.defineProperties(obj, props);
}
This is less sketchy than you would expect, has basically no performance penalty once the object is created, and behaves nicely with enumeration (for...in loops) and membership testing (in operator). Here's some example usage:
let test = multiKey([
['north', 'up'],
['south', 'down'],
['east', 'left'],
['west', 'right']
]);
test.north = 42;
test.down = 123;
test.up; // returns 42
test.south; // returns 123
let count = 0;
for (let key in test) {
count += 1;
}
count === 4; // true; only unique (un-linked) properties are looped over
Taken from my Gist, which you may fork.
Same reponse (ES6 Proxy, RegExp), but in a shorter way (and significantly less legible)
let align = new Proxy({
'start|top|left': -1,
'middle|center': 0,
'end|bottom|right': 1,
}, { get: (t, p) => Object.keys(t).reduce((r, v) => r !== undefined ? r : (new RegExp(v).test(p) ? t[v] : undefined), undefined) })
align.start // -1
align.top // -1
align.left // -1
align.middle // 0
align.center // 0
align.end // 1
align.bottom // 1
align.right // 1
//create some objects(!) you want to have aliases for..like tags
var {learn,image,programming} =
["learn", "image", "programming"].map(tag=>({toString:()=>tag }));
//create arbitrary many aliases using a Map
var alias = new Map();
alias.set("photo", image);
alias.set("pic", image);
alias.set("learning", learn);
alias.set("coding", programming);
//best put the original tagNames in here too..
//pretty easy huh?
// returns the image object
alias.get("pic");
// ;)
here is a way you can initialize an object with several keys sharing the same value
var holidays = {
...["thanksgiving day", "thanksgiving", "t-day"].reduce((acc, key) => ({ ...acc, [key]: 'foo' }), {})
}
although I would personally think it was more clear if it was written out
Object.fromEntries produces some fairly readable and concise code:
var holidays = Object.fromEntries(
["thanksgiving day", "thanksgiving", "t-day"].map(k => [k, "foo"]));
The spread syntax can be used to include this alongside other key/value pairs:
var holidaysAndMore = {
"A": "a",
...Object.fromEntries(
["thanksgiving day", "thanksgiving", "t-day"].map(k => [k, "foo"])),
"B": "b"
};

Categories

Resources