Multi level dynamic key setting? - javascript

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);

Related

React extracting a nested json object

How can I extract the 'jobs' object from a nested json list like this:
result:
{
person:
[
{
name: ""
address: ""
jobs: [
{
company:""
},
{
company:""
}
]
}
]
}
Thank you
Write a generic method to extract object properties.
function onExtract(key, data) {
if (isObject(data)) {
for (let item in data) {
if (key === item) {
return data[item];
}
const res = onExtract(key, data[item]);
if (res !== null) return res;
}
}
if (isArray(data)) {
for (let item of data) {
const res = onExtract(key, item);
if (res !== null) return res;
}
}
return null;
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === "[object Array]";
}
// test
const data = {
person: [
{
name: "",
address: "",
jobs: [
{
company: ""
},
{
company: ""
}
]
}
]
};
console.log(onExtract("jobs", data));
let's say you have a return var that contains this json value
let mappedCompanies = return.person.map(person =>
person.jobs.map(job => job.company)
).flatMap(m => m)
mappedCompanies would contain an array with all the companies names for each one of the registers in "person", all as one array of strings
you can read more about Array.map() here: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
A dynamic way to query the person[] and find jobs, is to use the javascript map() method.
Here is the code without comments.
const personsJobs = (personName, personAddress) => {
const jobs = result.person.map((el) => {
if (el.name === personName && el.address === personAddress) {
return el.jobs;
} else {
return null;
}
})
.filter((el) => el !== null);
return jobs;
};
console.log(personsJobs("wyatt", "1234 test ln"));
Here is the code with comments to explain how the personsJob function works.
// Blow is an ES6 arrow function with the parameters 'personName' and 'personAddress',
// which represents the person in which you are querying for jobs (using both a persons
// name and address so in the case of persons with the same name, you only find the jobs
// of the person you want).
const personsJobs = (personName, personAddress) => {
// Since 'person' is an array, we can use the 'map' method as stated before, which
// will create a new array (jobs) that will store the jobs a specific person has.
const jobs = result.person.map((el) => {
// el stands for the current position in the person array.
// if el's (the current person) name and address values are equal to that of the
// parameters personName and personAddress, then that persons jobs are added to the jobs // array, however, if el does not satisfy the two parameters, null is added to the jobs
// array.
// The array, upon completion, will look something like this: ["programmer", null, null]
if (el.name === personName && el.address === personAddress) {
return el.jobs;
} else {
return null;
}
})
// Finally, the filter method is called to remove all null values so that you will
// only have the persons job in the jobs array.
// After filtering, the array will look like this: ["programmer"]
.filter((el) => el !== null);
return jobs;
};
// Prints the array of wyatt's jobs
console.log(personsJobs("wyatt", "1234 test ln"));
So, following the conclusion of the function, you will have dynamically found the jobs of a specific person.
you can use flatMap function like:
const jobsData = result.person.flatMap(item => item.jobs);
Here is a flexible solution using object-scan
// const objectScan = require('object-scan');
const data = { person: [{ name: '', address: '', jobs: [{ company: '' }, { company: '' }] }] };
console.log(objectScan(['person[*].jobs'], { reverse: false, rtn: 'value' })(data));
// => [ [ { company: '' }, { company: '' } ] ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

Cloud Functions: Values not read in Map

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

filter array to product check in record for events

I have an activity feed, it contains a number of different types of activity for our site.
one type of activity is checkin. which logs when a user checks in and checkouts of a site.
The record entries look like so
Entryable_id | Entry_type | Action | timestamp
1 Ticket Update 12:01
3 Ticket New 12:07
4 CheckIn ClockedIn 14:30
4 CheckIn ClockedOut 17:30
What I want to do is create an array with entries in it like so
Entryable_id | ClockedIn| ClockedOut
4 14:30 17:30
so far what I have is
{
let staffCheckins = []
let checkinRecord = []
if (this.DiaryStore.entries.length) {
this.DiaryStore.entries.forEach(function(val) {
if (val.entryable_type === 'CheckIn') {
staffCheckins.push(val);
return val
}
})
}
staffCheckins.forEach(function(val) {
if (val.action === "ClockedIn") {
checkinRecord[val.entryable_id] = {
clockedIn: val.created_at,
user: val.user
}
}
if (val.action === "ClockedOut") {
checkinRecord[val.entryable_id] = {
clockedOut: val.created_at
}
}
})
console.log(completeCheckin)
},
which gives
1: clockedIn: "2019-07-22T10:26:45.000000Z",
2: clockedIn: "2019-07-22T12:38:02.000000Z"
so I assume that it is not appending to the key when i do
checkinRecord[val.entryable_id] = {clockedOut: val.created_at}
On top of that this all feels like a mess. is there a better way to filter and get what I need?
Thanks
You need to merge attribute, instead of assign to new object
staffCheckins.forEach(function(val) {
if (!checkinRecord[val.entryable_id]) {
checkinRecord[val.entryable_id] = {}
}
if (val.action === "ClockedIn") {
checkinRecord[val.entryable_id] = {
...checkinRecord[val.entryable_id],
clockedIn: val.created_at,
user: val.user
}
} else (val.action === "ClockedOut") {
checkinRecord[val.entryable_id] = {
...checkinRecord[val.entryable_id],
clockedOut: val.created_at
}
}
}
so I haven't gotten to test it because I'm out and about but you could try something like this. If they object entryable_id doesnt exist in the current object in the array, then it will create a new object with the members, otherwise it will find the object and update the fields
{
let staffCheckins = [];
let checkinRecord = [];
if (this.DiaryStore.entries.length) {
staffCheckins = this.DiaryStore.filter(val => val.entryable_type.toLowerCase() === 'checkin');
}
staffCheckins.forEach(function(val, i) {
let { action, entryable_id, created_at, user } = val;
if (!entryable_id in checkinRecord[i]) {
checkinRecord[i] = {
clockedIn: created_at,
clockedOut: created_at,
user
}
}
if (action.toLowerCase() === 'clockedin') {
checkinRecord[i] = {
...checkinRecord[i],
clockedIn: created_at,
user
}
} else if (action.toLowerCase() === 'clockedout') {
checkinRecord[i] = {
...checkinRecord[i],
clockedOut: created_at
}
}
});
}
apologies if I understood wrong but I'm also no currently at my actual computer to test any of it
You could do this whole operation in a filter reduce combination and create a groupBy object using the Entryable_id as keys.
Once loop completes get values array of that object
const checkinGroup = data.filter(({Entry_type}) => Entry_type === 'CheckIn')
.reduce((a, c)=>{
let {Entryable_id:id, Action, timestamp} = c;
a[id] = a[id] || {Entryable_id: id, ClockedIn: null, ClockedOut: null};
a[id][Action] = timestamp;
return a;
},{});
const res = Object.values(checkinGroup)
console.log(res)
<script>
const data = [{
Entryable_id: 1,
Entry_type: 'Ticket',
Action: 'Update',
timestamp: '12:01'
},
{
Entryable_id: 3,
Entry_type: 'Ticket',
Action: 'New',
timestamp: '12:07'
},
{
Entryable_id: 4,
Entry_type: 'CheckIn',
Action: 'ClockedIn',
timestamp: '14:30'
},
{
Entryable_id: 4,
Entry_type: 'CheckIn',
Action: 'ClockedOut',
timestamp: '17:30'
}
]
</script>

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

How can I improve this two-pass JavaScript data filter?

My app gets a list of items, each with a state property that can have a value of "foo," "bar," "blah," etc. Sometimes the list will have items that are identical except that one is a foo and the other is a bar. In that case I want to keep the foos and discard the bars.
My filter makes two passes over the list. On the first pass, when a foo is encountered I push a string--composed of "bar" plus an id value--to a blacklist array. Then on the second pass I generate the key again and filter out any items that are in the blacklist.
var myFilter = function (items) {
var blacklist = [];
_.each(items, function (x) {
if (x.state === 'foo') {
blacklist.push(['bar', x.id].join(''));
}
});
return _.filter(items, function (x) {
var key = [x.state, x.id].join('');
return !_.contains(blacklist, key);
});
};
This method seems pretty ugly and inefficient to me. Is there a better way?
_.each(items, function (x) {
if (x.state === 'foo') {
var key = ['bar', x.id].join('');
if ( key in items ) {
delete items[key];
}
}
});
something like this should be close, may need to be fixed a bit but its a single pass
Seems that reject() does everything you need here in a single pass:
var coll = [
{ state: 'bar'},
{ state: 'foo' },
{ state: 'bar' },
{ state: 'foo'}
];
_.reject(coll, { state: 'foo' });
// → [ { state: 'bar' }, { state: 'bar' } ]

Categories

Resources