Looking for eval() alternative when node/attribute name is variable/parameter - javascript

It is clear that when the attribute (for example id) exists/is defined in the script, we should use obj[id] and not eval('obj.' + id). However, I have not yet found a solution for the case where the attribute name is itself a variable and not defined - for example when recieved as a parameter. In my case I have node.js on the server recieving a JSON object requestData being sent from the client via socket.io. The requestData contains a key/value pair: requestData.key is an array of the node names that make up the JSON path to the element, and requestData.val is the value to be stored in that element.
In the server script there is a defined JSON object called root which contains information relevant to the app, for example:
"root": {
"pages": {
"TM1010": {
"appId": "TM1010",
"componentName": "UserTasksPage"
},
"TM1020": {
"appId": "TM1020",
"componentName": "UsersPage"
}
}
}
If the client were to send a request to update an element - for example to change the value of root.pages.TM1010.componentName - this is how it would be sent from the client:
let requestData = {'key': ['pages', 'TM1010', 'componentName'], 'val': newComponentName};
this.socket.emit('changeData', requestData);
And this is how it is recieved and executed on the server:
socket.on('changeData', (requestData) => {
var str = 'root'
var key = requestData.key
var len = key.length;
for (var i = 0; i < len; i++) {
str = str + '["' + key[i] + '"]'
}
str = str + '=requestData.val'
eval(str)
});
So that what is being executed in the end (the value of str) is:
root["pages"]["TM1010"]["componentName"]=requestData.val
How could this be done without eval() ?
As I mentioned, I do not know the names of those elements that will be sent from the client. All I have on the server is one JSON object called 'root' and I want to continuously add/update to it as I recieve data from the client.
I have tried solutions based on JEXL or mathjs but it doesn't seem to work for this type of case.

You could reduce the keys by taking the object and assign the value with the last key.
const
setValue = (object, keys, value) => {
var path = keys.slice(),
last = path.pop();
path.reduce((o, k) => o[k], object)[last] = value;
};
var data = { root: { pages: { TM1010: { appId: "TM1010", componentName: "UserTasksPage" }, TM1020: { appId: "TM1020", componentName: "UsersPage" } } } },
requestData = { key: ['pages', 'TM1010', 'componentName'], val: 'newComponentName' };
setValue(data.root, requestData.key, requestData.val);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Problem transferring a json string from the client to the esp8266 server

As part of my WLAN Thermometer project, I am planning a small file management for the files stored on the ESP. In this context, I need to transfer a list of file names from the client to the server. Because there is also the wonderful ArduinoJSON library for the ESP 8266, I would like to pass the data as a JSON object. The first excerpt from the scripts.js of my webpage shows how to create the filelist (contains all available files at ESP Filesystem) and compile and transfer the deletelist (whose elements should be deleted).
let fileID = 0
for (i = 2; i < FDatas.length; i += 2)
{
let fileInfo = {
name: FDatas[i],
size: FDatas[i+1],
fileID: fileID,
marked: false};
fileList.push(fileInfo);
};
}
function deleteFiles() {
let deleteFileList = [];
let fileID = 0;
for (let i = 0; i < fileList.length; i++) {
if (fileList[i].marked == true) {
let keyname = 'fileID_' + String(fileID);
fileID += 1;
let newEntry = {[keyname]:fileList[i].name}
deleteFileList.push(newEntry);
}
}
if (deleteFileList.length > 0) {
var xhttp = new XMLHttpRequest();
var formData = JSON.stringify(deleteFileList);
xhttp.open("POST", "/deleteFiles");
xhttp.send(formData);
}
}
On the server side, communication is organized as follows:
In the setup part of the arduino code:
webserver.on("/deleteFiles", HTTP_POST, deleteFiles);
In the handler:
void deleteFiles() {
String input = webserver.arg("plain");
Serial println(input);
DynamicJsonDocument doc(2048);
DeserializationError err = deserializeJson(doc, input);
if (err) {
Serial.println(F("deserializeJson() failed with code "));
Serial.println(err.f_str());
}
JsonObject obj = doc.as<JsonObject>();
// Loop through all the key-value pairs in obj
for (JsonPair p : obj) {
Serial.println(p.key().c_str());
if (p.value().is<const char*>()) {
auto s = p.value().as<const char*>();
Serial.println(s);
}
}
webserver.send(200);
}
The result of these efforts is sobering. Nevertheless, the Serial.println(input); - command outputs the following,
[{"fileID_0":"/settings.json"},{"fileID_1":"/tdata.js"},{"fileID_2":"/scripts.js"}]
the passage through the JSON object does not result in key value pairs.
Where is my mistake? Thank you very much for your good advice.
1. Udate:
After first comment (Thank You) I've changed the arduino-code to:
void deleteFiles() {
String input = webserver.arg("plain");
Serial.println(input);
DynamicJsonDocument doc(2048);
DeserializationError err = deserializeJson(doc, input);
if (err) {
Serial.println(F("deserializeJson() failed with code "));
Serial.println(err.f_str());
}
JsonArray arr = doc.to<JsonArray>();
for(JsonVariant v : arr) {
Serial.println(v.as<const char*>());
}
webserver.send(200);
}
Unfortunately, the result is the same. No result in the loop.
Your json object consists of an array(each element of an array is indexed by a number like 0, 1, 2...), and within the array, there are these 3 json objects. So to access the data of each array element, you do doc[0] and so on. You can then access each key value pair with doc[0]['key'] notation.
StaticJsonDocument<192> doc;
DeserializationError error = deserializeJson(doc, input);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
const char* element1 = doc[0]["fileID_0"]; // "/settings.json"
const char* element2 = doc[1]["fileID_1"]; // "/tdata.js"
const char* element3 = doc[2]["fileID_2"]; // "/scripts.js"

How to verify if a node that might either contain a null or a string in the response body using Postman?

I need to verify whether a particular node in the response body of an API is null or a string. How is it done using ChaiJS in postman tests?
Sample API response body:
[
{
"exercise_num": "1",
"expire_date": "2019-03-11T16:31:17.935Z",
"created_at": "2019-03-15T11:44:35.698Z"
},
{
"exercise_num": "2",
"expire_date": null,
"created_at": "2019-03-15T11:44:38.363Z"
}
]
I would like to verify that the expire_date node in the above sample API response body will either only contain null or a string data type and it won't return any other data type such as int, etc.
I have tried the following:
var jsonData = JSON.parse(responseBody);
pm.test('All expire_date contains either string or null', () => {
for (i = 0; i < jsonData.length; i++) {
if(jsonData[i].expire_date === null){
tests["expire_date is null"] = true;
}
else{
pm.expect(jsonData[i].expire_date).to.be.a('string');
}
}
});
The test passes.
I'm expecting if something like this can be done:
pm.test('All expire_date contains string', () => {
for (i = 0; i < jsonData.length; i++) {
pm.expect(jsonData[i].expire_date).to.be.a('string' || null);
}
});
I believe there is no direct way with || operator though, you can check with data types. As null is an object type in javaScript, write the test case as follows:
var jsonData = JSON.parse(responseBody);
pm.test('All expire_date contains string', () => {
for (i = 0; i < jsonData.length; i++) {
pm.expect(typeof jsonData[i].expire_date).to.be.oneOf(['string', 'object']);
}
});
That's probably an easy way, and can be used in simple checks. However, I'd recommend you to use the second way, i.e. using The fastest JSON Schema Validator.
var Ajv = require('ajv'),
ajv = new Ajv({logger: console}),
schema = {
"properties": {
"expireDate": {
"type": ["string","null"]
}
}
};
var jsonData = JSON.parse(responseBody);
pm.test('All expire_date contains string', function() {
for (i = 0; i < jsonData.length; i++) {
pm.expect(ajv.validate(schema, {expireDate: jsonData[i].expire_date})).to.be.true;
}
});
If you'd like you check this in multiple requests, put schema at collection level.

Javascript writing loop using value from json and passing it's nested data to loop as json object

I have a list of user uuids in myContactsUuids array, using forEach() method to loop through them and add user which is retrieved with new ChatEngine.User(uuid) function to myContacts array.
myContactsUuids = ["john_doe_001", "john_doe_005"];
// set a global array of users
myContacts = {};
myContactsUuids.forEach(function(uuid) {
myContacts[uuid] = new ChatEngine.User(uuid);
});
Now trying to rewrite this to do essentially the same, but have additional data nested under each uuid and pass that as JSON object with user uuid as string in ChatEngine.User() function.
I have user data now like this, though can format in in any way.
myContactsUuids = {"john_doe_001":{"username":"John Doe","avatar_url":"http://someurl"},"john_doe_003":{"username":"Another John Doe","avatar_url":"http://someurl"}};
and ChatEngine.User(uuid,data) function where uuid is user uuid string and data json object so for e.g. user in loop look like this :
new $scope.ChatEngine.User("john_doe_001", {"username":"John Doe","avatar_url":"http://someurl"});
Just not sure what would be the best way to write loop for this and pass needed data to it, and then add retrieved user to array like did in simplified function. Perhaps I could do that using each(), but not sure how to correctly.
#Ele solution works, just need result array to be in format like this:
{ "john_doe_001":{"uuid":"john_doe_001","data":{"username":"John Doe","avatar_url":"http://someurl"}},"john_doe_003":{"uuid":"john_doe_003","data":{"username":"Another John Doe","avatar_url":"http://someurl"}} }
You can use the function Object.entries along with the function map
var result = Object.entries(myContactsUuids)
.map(([uuid, data]) => ({ [uuid]: new $scope.ChatEngine.User(uuid, data) }));
Example snippet:
var ChatEngine = {
User: function(uuid, data) {
this.uuid = uuid;
this.data = data;
}
}
var myContactsUuids = {"john_doe_001":{"username":"John Doe","avatar_url":"http://someurl"},"john_doe_003":{"username":"Another John Doe","avatar_url":"http://someurl"}};
var result = Object.entries(myContactsUuids)
.map(([uuid, data]) => ({ [uuid]: new ChatEngine.User(uuid, data) }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using the function reduce to build the desired output:
{
"john_doe_1": {
"data": {"2": 1}
},
"john_doe_2": {
"data": {"a": 1}
}
}
var ChatEngine = {
User: function(uuid, data) {
this.uuid = uuid;
this.data = data;
}
}
var myContactsUuids = {"john_doe_001":{"username":"John Doe","avatar_url":"http://someurl"},"john_doe_003":{"username":"Another John Doe","avatar_url":"http://someurl"}},
result = Object.entries(myContactsUuids)
.reduce((a, [uuid, data]) => {
a[uuid] = new ChatEngine.User(uuid, data);
return a;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

javascript Create and find value in object

I have a JavaScript object with the following properties
{
name:"Request",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
},
{
name:"Waiting",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
}
I have a list that has status (name) and month ( data:[])
First I want to validate whether the object with the name eg "Request" exists.
If it does not exist, create a new object.
{ name:'Request', data:[]}.
If the name already exists, it will check if the month exists in the object array "data". If there is no month in the array you must enter it.
{name:'Request', data:[1]}
I'm not entirely sure what you want to do if everything is successful, but the code below should satisfy the requirements you have provided.
var objs = [{
name:"Request",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
},
{
name:"Waiting",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
}];
var month = 25;
var request = null;
// Validate whether the object with the name eg "Request" exists.
var requestExists = objs.some(function (elem) {
if (elem.name === "Request") {
request = elem;
return true;
}
});
//If it does not exist, create a new object. { name:'Request', data:[]}.
if(!requestExists) {
objs.push({name: "Request", data: []});
}
else {
// If the name does exist, check if the month exists in the "data" array.
// If there is no month in the array you must enter it. {name:'Request', data:[1]}
if (request.data.indexOf(month) === -1) {
request.data.push(month);
}
}
console.log(request);
console.log(objs);
var list = [{
name:"Waiting",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
}]
var request = list.filter(status => status.name == "Request");
if (request.length === 0) list.push({name : "Request", data : [1]});
else if (request[0].data.length === 0) request[0].data.push(1);
console.log(list)
Since with the OP's example a data structure has to be checked and repaired whilst following some rules, a more generic approach should look for kind of a specific sanitizer function that can be both operated by itself (providing the key manually) but also run as a reduce method, if it comes to counterchecking the data structure against more than one data item key (like "Request" as it got provided with the OP's original example).
A solution then most probably will be close to the next provided one.
The Array API Documentation of the Mozilla Developer Network does provide polyfills for Array.isArray and Array.prototype.findIndex
function sanitizeDataList(list, key) {
var
indexOfDataItem = list.findIndex(function (dataItem) {
return (dataItem.name === key);
}),
dataItem = list[indexOfDataItem];
if (!dataItem) {
list.push(dataItem = {
name: key
});
}
if (!Array.isArray(dataItem.data) || (dataItem.data.length <= 0)) {
dataItem.data = [1];
}
return list;
}
var
dataList = [{
name: "Request_A",
data: [1,2,3,4,5,6,7,8,9,10,11,12]
}, {
name: "Waiting_A",
data: [1,2,3,4,5,6,7,8,9,10,11,12]
}/*, {
name: "Request_B",
data: [1,2,3,4,5,6,7,8,9,10,11,12]
}*/, {
name: "Waiting_B",
data: []
}, {
name: "Request_C"
}, {
name: "Waiting_C",
data: [1]
}],
dataItemKeyList = ["Request_A", "Waiting_A", "Request_B", "Waiting_B", "Request_C", "Waiting_C"];
dataList = dataItemKeyList.reduce(sanitizeDataList, dataList);
console.log(dataList);
.as-console-wrapper { max-height: 100%!important; top: 0; }
An easy solution is to use indexOf. You would need for it to be in a variable.
var myarray = [{
name:"Request",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
},
{name:"Waiting",
data:[1,2,3,4,5,6,7,8,9,10,11,12]
}];
var myreturn = false;
for(i = 0; myarray.length > i; i++){
var data = myarray[i];
if(data.name == "Request"){
myreturn = true;
}
if (myreturn === true){
alert('sweet now do some code');
break;
}
}
My initial thought was incorrect, but using a For loop will work.

Async data in Vue.js filter

I have a filter to convert content with id into user name. For instance, it converts Thank you #id-3124324 ! into Thank you #Jack !.
var filter = function (content) {
var re = /\s#(id\-\d+)\s/g;
var matches = [];
var lastMatch = re.exec(content);
while (lastMatch !== null) {
matches.push(lastMatch[1]); // uid being mentioned
lastMatch = re.exec(content);
}
// TODO: query user name from matched id
// replace id with user name
// fake usernames here
var usernames = ['Random Name'];
for (var i = 0; i < usernames.length; ++i) {
content = content.replace(new RegExp(matches[i], 'g'), usernames[i]);
}
return content;
};
Vue.filter('username', filter);
But in my case, usernames should be achieved with AJAX of a query with id. How should I do it?
Anything you can do with a filter you can do with a computed. In Vue 2.0, there won't be filters, so you'll need to use computeds instead.
Fetching data asynchronously into a computed is a somewhat messy problem, which is why there is the vue-async-computed plugin. The difficulty is that the computed has to return a value synchronously, when it hasn't finished fetching data.
The solution is to have the computed depend on a cache of fetched data. If the needed value isn't in the cache, the computed kicks off the async process to fetch it and returns some placeholder value. When the process adds a value to the cache, the computed should notice the change in the cache and return the complete value.
In the demo below, I had to make an auxiliary variable, trigger, which I reference in the computed just so I know there's been an update. The fetch process increments trigger, which triggers the computed to re-evaluate. The computed doesn't notice when values are added to or updated in decodedIds. There may be a better way to deal with that. (Using async computed should make it a non-issue.)
vm = new Vue({
el: 'body',
data: {
messages: [
'Thank you #id-3124324!'
],
decodedIds: {},
trigger: 0
},
computed: {
decodedMessages: function() {
return this.messages.map((m) => this.decode(m, this.trigger));
}
},
methods: {
decode: function(msg) {
var re = /#(id\-\d+)/g;
var matches = msg.match(re);
for (const i in matches) {
const p1 = matches[i].substr(1);
if (!(p1 in this.decodedIds)) {
// Indicate name is loading
this.decodedIds[p1] = '(...)';
// Mock up fetching data
setTimeout(() => {
this.decodedIds[p1] = 'some name';
++this.trigger;
}, 500);
}
}
return msg.replace(re, (m, p1) => this.decodedIds[p1]);
}
}
});
setTimeout(() => {
vm.messages.push('Added #id-12345 and #id-54321.');
}, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div v-for="message in decodedMessages">
{{message}}
</div>

Categories