Javascript find and map values based on scheme throwing undefined - javascript

I'm working with Javascript to build a mapping function, that, given a scheme should find the mapper's object's value within an application variable.
My attempt at doing this results in an undefined error and I'm trying to figure out what I need to change to make it work correctly, I've put together a JS fiddle that can be found here as well.
const application = {
state: {
outgoings: {
credit: 0,
food: 0
}
}
}
const mapper = {
"AppFoodSpend": "outgoings.food",
"AppCreditSpend": "outgoings.credit"
}
for (const field in mapper) {
console.log(application.state[mapper[field]])
// expected results should be credit 0 and food 0
}
For further context, I have an object called application which contains some fields, they may not always be in the same order. My mapper object contains some key/value pairs, and it's the value that I'd like to try and find in application. for for instance, my console log should be retrieving something like:
application.state.outgoings.food
application.state.outgoings.credit
They may not always be in this order, so I need to (on each iteration of the mapper) find the key in my application variable.
What do I need to change here?

Strings like 'outgoings.food' are not valid to navigate a data structure. You could use some "lenses" lib or write something like this...
const mapper = {
"AppFoodSpend": ["outgoings", "food"],
"AppCreditSpend": ["outgoings", "credit"]
}
for (const field in mapper) {
console.log(
mapper[field].reduce(
(ctx, path) => ctx[path],
application.state
)
)
}

Related

Is there a better way to modify a nested item in a JavaScript object?

I know that eval is bad and all but I can't really find any better way of doing this.
I receive an input string (from a command prompt) and I need to modify some value of a nested item inside a stored JSON object representing a file system.
Here it is in more detail:
I am making an edit command for my command prompt, and its form is like this:
edit <filename>
My code receives <filename> and looks in a file system for it, to check its existence.
Here is an example file system (in JSON, Linux based):
{
"home": {
"default": {
"documents": {},
"tst": "This is a file"
}
}
"etc": {}
"tmp": {}
}
So if the user's PWD (present working directory) is /home, and they enter default/tst, they should be editing /home/default/tst.
This is how I tried:
var wdir=JSON.parse(localStorage.getItem("filesys")); // Whole dir (wdir) - Obtain the stored file system
var cdir=wdir; // Current dir (cdir)
var i; // loop var
var l=f.length; // f is something like ["default","tst"]
var runstr="wdir"; // The thing to eval
for(i=0;i<l-1;i++){ // Go through everything except the last (the last is the file)
if(f[i] in cdir){ // If it exists in the current dir
cdir=cdir[f[i]]; // Go into a directory and modify cdir, not wdir
runstr+="['"+f[i]+"']"; // Add more to runstr
}
}
cdir[n]=t.value; // t.value is the new file content
eval(runstr+'=cdir;'); // run runstr to modify value
localStorage.setItem("filesys",JSON.stringify(wdir)); // Save the file system
Side note: this works, I'm just looking for a better solution
I've heard that eval is 'evil' and also slow as the JS interpreter has to start again. Since I'm using it for such a simple task I thought that there must be another way.
What better way can I set the value of a key that could be nested inside objects inside objects?
If you need any more information, you can ask in the comments and I will add it to my question.
Fun to code, the below snippet should do the trick ! You might need to add some additional conditions in order to prevent your user to override a folder !
I used the spread operator to do a "deep copy" of the object, if you want to directly alter the object passed to the function you can remove the first line and replace deepCopy by obj in the reducer. :)
const setObjectPropertyAtPath = (obj, path, value) => {
// we do a deep copy of the object because we want to alter this copy and not directly the object passed to the // function. Doing it provide us a way to return the modified object instead of direcly altering the original // object
const deepCopy = {...obj}
path.reduce((acc, cur, level) => {
if(level === path.length - 1) {
acc[cur] = value;
return value;
}
return acc[cur];
}, deepCopy)
return deepCopy;
}
const fs = {
"home": {
"default": {
"documents": {},
"tst": "This is a file"
}
},
"etc": {},
"tmp": {}
}
const updatedFs = setObjectPropertyAtPath(fs,["home", "default", "tst"], "foo")
console.log(updatedFs)

Setting Socket.io room variables

I would like to store some information in the socket room variables, but am getting the following error: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined
This is my code:
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
io.sockets.adapter.rooms[socket.roomId].isOpen = true;
io.sockets.adapter.rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
If I try to log socket.roomId it would return something like rBAhx0. And when I log io.sockets.adapter.rooms, I get the following:
Map {
'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}
However, when I try to log io.sockets.adapter.rooms[socket.roomId], it returns undefined. How can I fix this?
Socket.io made some breaking changes in the newest version and rooms cannot be accessed like they used to. It changed a lot of objects and arrays into Maps and Sets, you can see it in the logs you've posted.
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
Accessing properties of a Map works differently than accessing properties of a normal Object. Example:
const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar
Same applies to Sets, however in your case querying this Set in particular is probably a wrong approach, as this set only holds a collection of ids, and not actual room objects. Even if you were to get a value out of the Set, you could not access it's properties (host, isOpen and players) since it is only a string.
The version 3.0 made accessing the list of all rooms directly impossible I'm afraid. However the adapter now has a property socketRooms, which can be used in place of it.
In order to access rooms of a socket easier, you should access them like so:
io.sockets.adapter.socketRooms(socketId);
However that would still just return a list of strings.
The simplest solution to this problem would be to create an external variable outside of the connection scope.
const rooms = {};
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
rooms[socket.roomId].host = socket.username;
rooms[socket.roomId].isOpen = true;
rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
As you can see in your own log, io.sockets.adapter.rooms is a Map object (in the later versions of socket.io), not a plain object. That means that a room is accessed with .get(), not with plain indexing such as what you are using:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
That simply won't work because there is no plain socket.roomId property on the Map.
If you wanted to get the room object, you would have to do this:
let roomObj = io.sockets.adapter.rooms.get(socket.roomId);
roomObj.host = socket.username;
In my opinion, I wouldn't mess with the room objects directly. We already know that socket.io has changed their design before, rendering any code that tried to use them as broken and in need of redesign. Instead, I'd probably just create my own data structure where I stored my own data and then I won't be dependent upon the whims of how socket.io changes their design. Accessing the room objects directly is not a committed design or committed API of socket.io. They can change how it works whenever they want and have, indeed, changed it recently.
After the breaking update from Socket.io of changing Array to Map.
A simple solution would be changing:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
to:
io.sockets.adapter.get(socket.roomId).host = socket.username;

filter map result with duplicate keys

I have a command handler that uses a map with commands assigned from user command folders and a general comm folder. User can add their own commands. the map filters using name inside the file which is also how the call them. How do I make a comparison if there is more than one with the same name? Afaik maps can't do this and fear I have to redo it all.
At first I was trying to assign every user it's own map
client.User1.get(command, argument);
client.User2.get(command argument);
was this actually the right way to do it? I am using a .JSON with enabled command names for initial comparison. Below some of the code, as you ccan probably tell i'm not that experienced.
const assignedChannels = client.opts.channels.slice(0);
assignedChannels.unshift('#basiccommands');
const rootForCommands = "./commands";
client.commandCall = new Map();
for (const channelspecificDir of assignedChannels) {
const commandFiles = fs.readdirSync(`${rootForCommands}/${channelspecificDir}`);
for (const file of commandFiles) {
const commandCollection = require(`${rootForCommands}/${channelspecificDir}/${file}`);
//sets a new item in the collection.
//Key of map as command name and the value the function to send message and argument true or false
client.commandCall.set(commandCollection.name, commandCollection);
const currentProfile = JSON.parse(fs.readFileSync(`./userProfiles/${channel}.json`, 'utf8'));
if (!currentProfile.all_commands.includes(commandPre)){
return console.log("Command does not exist");
}
try {
client.commandCall.get(commandPre, argU).execute(channel, argU);
} catch (error) {
console.log('command broke');
console.error(error);
}
My prevoious method was to assign user specific folder and general command folder to a map and at the end of each iteration change the name of the map object. But that does not work for some reason.
Actually, you can use symbols as keys for maps
const map = new Map()
map.set(Symbol(‘the same name’), value1)
map.set(Symbol(‘the same name’), value2)
You can use any values for Map’s keys, even objects and functions
map.set({}, value3)
map.set({}, value4)
In all previous expressions we get different values (links) for Map’s keys.
BTW. Be aware with const some = require(‘some_file_name’).
require() caches a value for a file name. So, if your files may be updated during a run time and you need to read these updates you shouldn’t use ‘require()’. It’s good only for importing modules and static values (e.g. configs)

'to.include' is not working where 'to.eventually.equal' is working

I was trying to check whether a string is present in another string.I am using the below code
expect(actualTimeZone).to.include(employee.timeZone);
But it is giving the below error
AssertionError: object tested must be an array, a map, an object, a set, a string, or a weakset, but object given
But when I tried with the below code, it is not throwing the above error
expect(actualTimeZone).to.eventually.equal(employee.timeZone);
I am working on protractor framework with cucumber and javascript. Could someone help to solve this issue
I tried using the below code and it is working
expect(actualTimeZone).to.eventually.include(employee.timeZone);
The error:
object tested must be an array, a map, an object, a set, a string, or
a weakset, but object given
happens when you are returning a set of web elements (which is an object not an array) and you try to treat them like an regular array.
So, first you have to extract the array from the object, then map each element getting the desire attribute, and finally filtering to prevent empty or null values.
Below an example step by step, using cucumber and JavaScript in Cypress.
Cypress.Commands.add('getTableData', () => {
// This is the table in the front-end, we read it completely and save it in an alias called table
cy.get('[data-test="table-body"]').find('tr').as('table')
// Now we apply the logic explained before
cy.get('#table').then(row => {
let arr = Object.keys(row)
return arr.map(element => { return row[element].textContent })
.filter(element => element)
})
})
Now, we can use the method above and use it to execute any expect like .includes (which was your issue)
And('Verify the value is in the table', () => {
cy.getTableData().then(arr => {
expect(arr).includes(assetName)
})
})
Also, with the same method we can loop through the entire array and do anything with each element:
And('Verify the value is in the tablex', () => {
cy.getTableData().each(element => {
cy.log(element)
})
})

React native Array.find is not a function

I'm a junior in js development so this might be a silly question, so apologies.
I'm using firebase in my app to store my strings, I call it once when the app is loaded and keep it in memory for faster access.
Then I use strings.find to find a specific string in the array. All work find when running on iOS, but when running on Android I keep getting this weird error
TypeError: Undefined is not a function (evaluting 'this.strings.find...
Here's my data schema
{'strings': [{'name':'something', 'value':'something'} ... ]
And here's my code
getString(name) {
return this.strings.find(x => x.name == name).value
}
And this is where I define the object
init(onUpdated) {
Promise.all([stringsDb.once('value'), multiLang.once('value')])
.then(([stringsSnapshot, multiLangSnapshot]) => {
this.strings = stringsSnapshot.value
this.multiLang = multiLangSnapshot.value
onUpdated(true)
})
.catch((err) => {
console.log(err.stack)
onUpdated(false)
});
}
This probably happens because the this.strings is not an array. you need to debug it and see if its actually an array or not, my guess is it returns an object, if so then you need to use a reducer or perhaps Object.values or something to convert to an array.
p.s you should use the === operator when comparing the strings

Categories

Resources