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);
Related
I have an object obj which has n number of possible properties
lets say some of them are known,
const someKnownProps = ["props.abc", "xyz"]; // or more
I want to know if obj has other than known properties in it.
To clarify:
obj can look like this:
obj = {
props: {
abc: {
def: 1
},
ghi: {
jkl: 2
}
},
xyz: 3
}
Doing Object.keys only return first level children,
in this case it will return props not props.abc
You can use Object.keys to get all keys and filter the keys which aren't included in the someKnownProps array.
const obj = {
"props.abc": 1,
"xyz": 2,
"three": 3,
"four": 4,
}
const someKnownProps = ["props.abc", "xyz"]; // or more
const unknownKeys = Object.keys(obj).filter(key => !someKnownProps.includes(key))
console.log(unknownKeys)
There are two (unrelated) tasks involved in this question:
Traversal of an object's properties
Comparison of a set of traversed object properties to a list of strings representing dot-notation-formatted object property accessors
While I'm sure the former has been previously discussed on SO, I'll provide an implementation of such an algorithm below in order to address the details of this question.
This is essentially a specific case of recursion where each cycle starts with these inputs:
an object
a dot-notation-formatted path
a Set of existing such paths
The code below includes inline comments explaining what's happening, and there are some console.log statements at the end to help you visualize some example results based on the data in your question. If something is unclear after reviewing the code, feel free to leave a comment.
'use strict';
/** #returns whether value is a non-null, non-array object */
function isObject (value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/** #returns the enumerable (optionally including inherited) keys of an object */
function getKeys (obj, includeInherited = false) {
if (!includeInherited) return Object.keys(obj);
const keys = new Set();
let o = obj;
while (o !== null) {
for (const key of Object.keys(o)) keys.add(key);
o = Object.getPrototypeOf(o);
}
return [...keys];
}
/**
* #returns an array of strings representing all traversible branches
* of child objects, each formatted as a combined path of dot-notation
* property accessors
*/
function findObjectPaths (
obj,
{
includeInherited = false,
currentPath = '',
paths = new Set(),
skipReturn = false,
} = {},
) {
for (const key of getKeys(obj, includeInherited)) {
// Append the current dot-notation property accessor
// to the existing path of this object:
const path = `${currentPath}.${key}`;
// Add it to the set:
paths.add(path);
const o = obj[key];
// Recurse if the child value is an object:
if (isObject(o)) {
findObjectPaths(o, {
includeInherited,
currentPath: path,
paths,
skipReturn: true,
});
}
}
// If this is not a sub-cycle (it's the top-level invocation), then convert
// the set to an array and remove the first "." from each string
if (!skipReturn) return [...paths].map(p => p.slice(1));
}
// Use:
const obj = {
props: {
abc: {
def: 1,
},
ghi: {
jkl: 2,
},
},
xyz: 3,
};
let someKnownProps = ['props.abc', 'xyz'];
let objectPaths = findObjectPaths(obj);
let hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // true
// An example of all of the paths in the object above:
someKnownProps = [
'props',
'props.abc',
'props.abc.def',
'props.ghi',
'props.ghi.jkl',
'xyz',
];
objectPaths = findObjectPaths(obj);
hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // false
// Finally, comparing the results of inherited vs non-inherited enumeration:
const objWithoutOwnProps = Object.create({
props: {
abc: {
def: 1,
},
ghi: {
jkl: 2,
},
},
xyz: 3,
});
console.log(
'Non-inherited props:',
findObjectPaths(objWithoutOwnProps),
);
console.log(
'Inherited props:',
findObjectPaths(objWithoutOwnProps, {includeInherited: true}),
);
Similar to what Mina said:
let obj = {one: 1, two: 2, three: 3};
let knownKeys = ['one', 'two'];
for (let key in obj) {
if (!knownKeys.includes(key)) {
console.log(key);
}
}
I want to merge values from an array into a static nested object. The array containing the values is something like this,
['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27']
and the object in which the values has to be merged is,
const person = {
details_1: {
name: null,
hobbies: null,
profession: null
},
details_2: {
age: null
}
};
I want my output object to look like below,
const updated_person = {
details_1: {
name: 'ABC XYZ',
hobbies: [M,N,O,P],
profession: 'S'
},
details_2: {
age: 27
}
};
Thanks a lot for your help!
I made another solution with a different approach.
Here I used an interface weher I described the desired data structure.
In the second part the string array is tranformed into key and value pairs. Thereform are filtered the keys of interface and added into an empty object literal.
const data = ["name=ABC XYZ", "hobbies=[M,N,O,P]", "profession=S", "age=27"];
const dataInterface = {
details_1: { name: null, hobbies: null, profession: null },
details_2: { age: null },
};
function orederData(arr) {
const record = arr.map((item) => {
let [key, value] = item.split("=");
if (value[0] === "[" && value[value.length - 1] === "]") {
value = value.slice(1, value.length - 1).split(",");
}
return { key, value };
});
const dataBlock = {};
Object.keys(dataInterface).map((detail) => {
dataBlock[detail] = {};
Object.keys(dataInterface[detail]).forEach((dataKey) => {
dataBlock[detail][dataKey] = record.filter((record) => {
return record.key === dataKey;
})[0].value;
});
});
return dataBlock;
}
const orderedData = orederData(data);
console.log(orderedData);
You can simply achieve this by iterating the input array.
const arr = ['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27'];
const person = {
details_1: {},
details_2: {}
};
arr.forEach(item => {
(item.split('=')[0] !== 'age') ? person.details_1[item.split('=')[0]] = item.split('=')[1] : person.details_2[item.split('=')[0]] = item.split('=')[1]
});
console.log(person);
There is no way to cleanly merge an unstructured array into a structured object such that the array values end up in the appropriately keyed person properties.
javascript does provide the assign() function that merges objects but for YOUR requirements your source data needs to be an object similarly structured and not an array.
so this:
['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27']
would need to become this:
const source= [{details_1: {"name":"ABC XYZ", "hobbies":"[M,N,O,P]", "profession":"S"}, details_2: {"age":"27"}}]
such that a call to Object.assign():
const new_person = Object.assign(person, source[0]);
fills this
const person = {
details_1: {
name: null,
hobbies: null,
profession: null
},
details_2: {
age: null
}
};
properly, though you may need to clone or instantiate and empty person first.
or, if person is an Object you could have a fill() method that knows what to do with the array data.
Given EcmaScript Section 12.6.4:
Properties of the object being enumerated may be deleted during
enumeration. If a property that has not yet been visited during
enumeration is deleted, then it will not be visited.
However, check out this case:
class Lead {
MAPPINGS = [
[
/Medication_Types_(\d+)/,
(data, key, acc, i) => {
const values = Object.keys(data)
.filter( key => /Medication_Types_(\d+)/.test(key ))
.map(key => data[key]);
console.log('the values: ', values);
acc['Medication_Types'] = values;
Object.keys(data).forEach( key => {
if( /Medication_Types_(\d+)/.test(key)) delete data[key];
})
return i;
}
]
]
format(data) {
const copy = JSON.parse(JSON.stringify(data));
let mapping = false;
let i = 1;
const result = Object.keys(copy).reduce((acc, key) => {
mapping = false;
this.MAPPINGS.forEach( (mapping) => {
if(mapping[0].test(key)){
mapping[1](copy, key, acc, i);
mapping = true;
}
});
if(!mapping){
acc[key] = copy[key];
}
return acc;
}, {});
return result;
}
}
const data = {
"Gender" : "Male",
"Medication_Types_1" : "A",
"Medication_Types_2" : "B",
"First_Name" : "bob"
}
const lead = new Lead();
const result = lead.format(data);
console.log('the result: ', result);
It outputs:
the values: [ 'A', 'B' ]
the values: []
the result: {
Gender: 'Male',
Medication_Types: [],
Medication_Types_1: undefined,
Medication_Types_2: undefined,
First_Name: 'bob'
}
If the keys were truly deleted, then "the values" would have not printed the second time and instead I would have gotten (and what I want) this:
{
Gender: 'Male',
Medication_Types: ['A', 'B'],
First_Name: 'bob'
}
So it seems like delete might be deleting the key but not from the current iteration, but EcmaScript states it will delete from the current iteration! What's wrong and how can I fix it?
You are not doing an enumeration. You are creating an array, and then deleting the properties during the forEach iteration of that array:
const keys = Object.keys(data);
console.log(keys); // at this point, all the keys are already in the array
keys.forEach( key => {
if( /Medication_Types_(\d+)/.test(key)) delete data[key];
})
When you delete the property from the object, it does not get removed from the keys array. Same happens in your reduce call. You could avoid that by using a real for … in loop.
However, the actual bug in your code is
mapping = false;
this.MAPPINGS.forEach((mapping) => {
// ^^^^^^^
if (mapping[0].test(key)){
mapping[1](copy, key, acc, i);
mapping = true;
// ^^^^^^^
}
});
where you do not set the outer-scope boolean mapping variable to true that would prevent the value getting copied into the result, but instead you just overwrite the mapping parameter of your forEach callback.
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
code1:
let commap = new Map();
data.forEach(function(item,index){
if(commap.has(item.comp)){
let arr = companyset.get(item.comp);
arr.push(item);
commap.set(item.comp,arr);
}else{
commap.set(item.comp,[item]);
}
});
code2:
let commap = new Map();
data.forEach(function(item,index){
commap .set(item.comp,commap.has(item.comp)?commap.get(item.comp).push(item):[item]);
});
The logic of the two pieces of code is the same, if there is more than one entry for company,code2 throw an error:'Uncaught TypeError: commap.get(...).push is not a function',
why did this error occur,is it the order of js execution?
Thanks a lot
The problem is that .push returns the new length of the array, not the array itself. So, when you set the value to companyset.get(item.company).push(item), you're setting the value to a number, not an array, which means when you try to push to it later, an error is thrown.
Remember that arrays, being non-primitive, can be changed simply with a reference to the array, without having to re-assign it. You only have to set the first time you come across a new company. Try something like this instead:
const data = [
{ company: 'foo' },
{ company: 'bar' },
{ company: 'foo' },
{ company: 'foo' },
];
const companyset = new Map();
data.forEach(function(item) {
const { company } = item;
const arr = companyset.get(company);
if (!arr) return companyset.set(company, [item]);
arr.push(item);
});
console.log(companyset.get('foo'));
But to combine an array into an object, it would be more appropriate to use reduce instead of forEach:
const data = [
{ company: 'foo' },
{ company: 'bar' },
{ company: 'foo' },
{ company: 'foo' },
];
const companyset = data.reduce((map, item) => {
const { company } = item;
const arr = map.get(company);
if (!arr) map.set(company, [item]);
else arr.push(item);
return map;
}, new Map());
console.log(companyset.get('foo'))