I know lots of people think "eval is evil," but I have to accomplish something and I'm having trouble figuring out how to do it without eval().
The situation is this: an external file (I have no control over it--EDIT: but it's not user-generated. It's from a trusted source! I imagine this is important) is spitting out JavaScript for me to use. This JavaScript contains some nice JSON data (which is what I need to get), but it's flanked by ordinary JavaScript statements declaring variables and calling functions and such. It looks kinda like this:
var foo = new Object();
foo['KEY'] = {Field1: 'Value1', Field2: 'Value2'};
eval('fooFunction(foo)');
If I eval() this, I can just parse foo['KEY'] and be done with it. The only way I can think to do this without eval() is with a bunch of annoying replace()ments, which hardly seems better. Am I missing some obvious way to do this? Most of the "you don't have to use eval()" alternatives I usually see assume I have complete control over everything, but in this case I have to work around this existing code.
EDIT: I should add that this code is being obtained via an AJAX call from a proxy script (cross-domain stuff), so none of the variables are accessible. If they were, I'd obviously just be able to parse foo['KEY'] and be on my merry.
SECOND EDIT: nothing conclusive yet! I'm getting dangerously close to concluding that eval() is the way to go. Can you stomach this outcome? I'm about to give in to evil(). Somebody stop me, because it's looking like the only way.
The external code better send back valid JSON. The value in your example is not valid JSON, as the keys must be wrapped with double quote.
I came up with small pure JavaScript parser, that can handle simple invalid JSON by adding double quotes by itself. It currently won't support non string values.
function ParseRawJSON(rawCode) {
var arrCandidates = [];
var lastOpenBracketIndex = -1;
for (var i = 0; i < rawCode.length; i++) {
var curChar = rawCode.charAt(i);
if (curChar === "}") {
if (lastOpenBracketIndex >= 0) {
arrCandidates.push(rawCode.substr(lastOpenBracketIndex, i - lastOpenBracketIndex + 1));
lastOpenBracketIndex = -1;
}
} else if (curChar === "{") {
lastOpenBracketIndex = i;
}
}
var arrJsonObjects = [];
for (var i = 0; i < arrCandidates.length; i++) {
var currentJSON = null;
try {
currentJSON = JSON.parse(arrCandidates[i]);
} catch (e) {
//try fixing
var fixedCandidate = TryFixJSON(arrCandidates[i]);
if (fixedCandidate) {
try {
currentJSON = JSON.parse(fixedCandidate);
} catch (e) {
currentJSON = null;
}
}
}
if (currentJSON != null) {
var keys = [];
for (var key in currentJSON)
keys.push(key);
if (keys.length > 0)
arrJsonObjects.push(currentJSON);
}
}
return arrJsonObjects;
function Trim(s, c) {
if (c instanceof Array) {
for (var i = 0; i < c.length; i++)
s = Trim(s, c[i]);
return s;
}
if (typeof c === "undefined")
c = " ";
while (s.length > 0 && s.charAt(0) === c)
s = s.substr(1, s.length - 1);
while (s.length > 0 && s.charAt(s.length - 1) === c)
s = s.substr(0, s.length - 1);
return s;
}
function TryFixJSON(strBlock) {
if (strBlock.indexOf(":") <= 0)
return false;
strBlock = strBlock.replace("{", "").replace("}", "");
var mainParts = strBlock.split(",");
for (var i = 0; i < mainParts.length; i++) {
var currentPart = Trim(mainParts[i]);
if (currentPart.indexOf(":") <= 0)
return false;
var subParts = currentPart.split(":");
if (subParts.length !== 2)
return false;
var currentKey = Trim(subParts[0], [" ", "'", "\""]);
var currentValue = Trim(subParts[1], [" ", "'", "\""]);
if (currentKey.length === 0)
return false;
subParts[0] = "\"" + currentKey + "\"";
subParts[1] = "\"" + currentValue + "\"";
mainParts[i] = subParts.join(":");
}
return "{" + mainParts.join(", ") + "}";
}
}
This will just look for anything between { and } and try to parse as JSON. No eval, in case of failure it'll just ignore the invalid block. Success? Great, it will return plain array of the valid JSON's it found.
Usage example:
var rawCode = "var foo = new Object(); { dummy here }}} function boo() {}" +
"foo['KEY'] = { \"Field1\": \"Value1\", \"Field2\": \"Value2\"}; hello {\"foo\": \"bar\"} and it's over ";
var jsonObjects = ParseRawJSON(rawCode);
for (var i = 0; i < jsonObjects.length; i++) {
for (var key in jsonObjects[i]) {
var value = jsonObjects[i][key];
//got key and value...
}
}
Live test case, using fixed version of your sample code.
A generally safer alternative to using eval is creating a new Function and passing it the string function body. That way (unless something is explicitly acessing the window object) you won't have access to the global scope and can keep it encapsulated in the function scope.
Let's say the first two lines of your example code are the JavaScript that you'd like to evaluate, if you know the name of the variable you want to retrieve as a JSON object you can just return it at the end of the created function and then call it:
var js = "var foo = {}; foo['KEY'] = {Field1: 'Value1', Field2: 'Value2'};";
var fn = new Function(js + ';return foo;');
var result = fn();
console.log(JSON.stringify(result));
This is also what MDN suggests doing in the documentation for eval:
More importantly, third party code can see the scope in which eval() was invoked, which can lead to possible attacks in ways of which the similar Function is not susceptible.
If the JSON contains just data and not functions you can use JSON.parse()
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse for more detailed info.
As the method has been placed in to the global, then you can do
window["fooFunction"](foo)
Related
Ok, maybe is not the best title, but I lacked inspiration, so here goes:
Let's say you have a "global" (not really) variable to store temporary data and sub data as random users interact with your server. Normally on the first interaction with your server, the main variable will be undefined so you need to handle that case.
Now, what puzzled me about this, is what's the best practice performance wise to do this if there are a lot of users and a lot way more interactions with the variable.
Puzzled? Yeah, I know, words are not my strong point so let me show you in code
So you have
var user_data = [];
Then a function that handles user interaction to store data
function writeData(uid, data_name, data)
Now, on first interaction, user_data[uid][data_name] is undefined, and so it's user_data[uid]
I know you can handle this 2 ways:
With if -
if(!user_data[uid]) user_data[uid] = {}
user_data[uid][data_name] = data
With try/catch
try{user_data[uid][data_name] = data}
catch(e) {user_data[uid] = {}; writeData(uid, data_name, data)}
The if will check on every interaction, and like I said there are a lot.
Try catch will trigger once, but it has a cost as a block (afaik)
Which one is better? Or is there a another better way
#Nertan ,
There is a partiality in your proof :P . I have slightly tweeked the ternary way (same as the order of execution in if way). With this you can conclude.
//var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = Date.now();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = Date.now()-t;
return tf;
}
function setValue_Opp(uid,ud,value)
{
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)
I feel we can take advantage of ES6 ternaries as below:
let user_data = {}
const writeData = (uid, data_name, data) => {
((user_data[uid] || (user_data[uid] = {})) && (user_data[uid][data_name] = data ))
console.log(user_data)
// perform write action
}
writeData('1',"test","test1");
writeData('2',"test","test2");
writeData('1',"test","test3");
Ok, so I had to rewrite the test because it doesn't work fine in the Snippet
So I made this for node.js:
var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = present();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = present()-t;
console.log("END "+val+" at "+tf);
return tf;
}
function setValue_Opp(uid,ud,value)
{
(ud[uid] || (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)
And in the end:
3244.328997004777 3695.0267750024796 3437.6855720058084
Which means:
The best is the classical if
The second best is condintional operators method
And the worst is the try-catch
So it seems the classics win
Edited:
With further tests thanks to #CRayen the best method is :
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);
Sorry for the Lost Post . The problem statement
I have this URL -
http://www.XXXXX.com/mobiles-tablets/mobiles/apple?q=&idx=letsTango_default_products&p=0&hFR%5Bcategories.level0%5D%5B0%5D=Mobiles%20%26%20Tablets%20%2F%2F%2F%20Mobiles%20%2F%2F%2F%20Apple&nR%5Bprice.AED.default%5D%5B%3C%3D%5D%5B0%5D=2560&is_v=1
I had to extract all the query-params in a object like a key value pair .
and then check from a different object if any of these query-params with same key has any matching value .
This is my solution , I am new to java script and so i want to confirm if this is the right way to go about.
The solution so far works for all the cases . just let me know if the performance can be enhanced or any better way.
Fiddle Link for the Solution - http://jsfiddle.net/rahulsingh09/tqpzv/6/
code
var callingUrl = "http://www.letstango.com/mobiles-tablets/mobiles/apple?q=&idx=letsTango_default_products&fsrc=sort_price,brand&p=0&hFR%5Bcategories.level0%5D%5B0%5D=Mobiles%20%26%20Tablets%20%2F%2F%2F%20Mobiles%20%2F%2F%2F%20LG&nR%5Bprice.AED.default%5D%5B%3C%3D%5D%5B0%5D=2560&nR%5Bprice.AED.default%5D%5B%3E%3D%5D%5B0%5D=1138&is_v=1";
if (callingUrl) {
var queryParam = {};
var split = callingUrl.split("?");
if (split.length > 1) {
var query = split[1].split("&");
if (query.length > 1) {
_.each(query, function(q) {
var key = decodeURIComponent(q.split("=")[0]);
var value = decodeURIComponent(q.split("=")[1]);
if (value.indexOf(",") !== -1) {
value = value.split(",");
}
queryParam[key] = value;
})
}
}
}
if (!_.isEmpty(queryParam)) {
var value = {
"fsrc": ["brand"]
};
for (var key in value) {
if (value.hasOwnProperty(key)) {
for (var k in queryParam) {
if (queryParam.hasOwnProperty(k)) {
if (k === key) {
var index = _.intersection(value[key], queryParam[k]);
if (index.length > 0) {
console.log("Matches");
} else {
console.log("fails");
}
}
}
}
}
}
please note - the url can change depending upon the browser url.
Thanks in advance , I want to get the best performance possible for this problem.
Here I have global variable userId, and i want to update it inside signInUserFunction(), to use is in other function. I have tried to define it using var, window, But all these didn't help. This variable doesn't update. As i see its about AJAX async. So, what can i do with it?
And yes, I know that its not good to make authentication with JS, I am quite new to it. So, I am just creating random methods to improve.
var userId = 1;
function signInUser() {
$.getJSON('http://localhost:8887/JAXRSService/webresources/generic/getAllUsers', function(data) {
var items = [];
var i = 0;
$.each(data, function(firstname, value) {
var str = JSON.stringify(value);
data = JSON.parse(str);
var innerId;
for (p in data) {
innerId = data[p].id;
if ($('#nameSignIn').val() == data[p].first_name && $('#passwordSignIn').val() == data[p].password) { //
userId = innerId;
window.location.href = "content.html";
break;
} else {
i++;
if (i == data.length) {
alert("Ощибка в логине или пароле!")
}
}
}
});
});
}
How are you determining whether or not it has been set? It looks like immediately after you set it, you navigate to a different page. When you get to that page, you will have an entirely new window.
Try alerting the value before navigating away.
EDITED: Here is how you could pass it to the other page (but you shouldn't do this in a real app)
window.userId=innerId;
alert(window.userId);
//this isn't a very secure way to do this. I DON'T recommend this
window.location.href = "content.html?id=" + innerId ;
Then in the other page, you could access it off the document.location:
alert(document.location.toString().split("?id=")[1]);
After reading my comments, you may want to try this:
var userId = 1;
function signInUser(){
$.getJSON('http://localhost:8887/JAXRSService/webresources/generic/getAllUsers', function(data){
var items = [], actors = data.Actors, l = 0;
$.each(actors, function(i, o){
l++;
if($('#nameSignIn').val() === o.first_name && $('#passwordSignIn').val() === o.password){
userId = o.id;
// this will redirect before any other code runs -> location = 'content.html';
if(l === actors.length){
alert('End of Loop');
}
}
});
});
}
signInUser();
I would not store sensitive data in JSON such as passwords. Use a database. There is no need to get all the data at the same time either.
Using the idea #mcgraphix proposed (and giving you the same warning...this would certainly not be the way to transfer data like this in a production environment), here is one way to do it:
function signInUser() {
var url = 'http://localhost:8887/JAXRSService/webresources/generic/getAllUsers';
var userId;
$.getJSON(url, function(data) {
$.each(data.Actors, function(index, actor) {
// Cache the values of the #nameSignIn and #passwordSignIn elements
var name = $('#nameSignIn').val();
var password = $('#passwordSignIn').val();
if (actor.first_name === name && actor.password === password) {
// We have found the correct actor.
// Extract its ID and assign it to userId.
userId = actor.id;
window.location.href = "content.html?userId=" + userId;
}
});
// This alert should only be reached if none of the actor objects
// has a name and password that matches your input box values.
alert("Ощибка в логине или пароле!");
});
}
// On the next page...
// Top answer from http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
// This approach can handle URLs with more than one query parameter,
// which you may potentially add in the future.
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
console.log('Query variable %s not found', variable);
}
var userId = getQueryVariable('userId');
Thanks you for help.Ended it all with usage of:
sessionStorage.getItem('label')
sessionStorage.setItem('label', 'value')
I can't add new name and value ff. this given condition:
$.each(names, function (i, name) {
$.get('https://www.example.com/path/' + name, function (data) {
var arrNow = CSVToArray(data, ',');
allArr.push(arrNow);
counter++;
if (counter === names.length) {
for (var j = 0; j < allArr.length; j++) {
for (var k = 1; k < allArr[j].length; k++) {
//console.log(allArr[j][k][0] + ': ' + allArr[j][k][1]);
//var f = moment(allArr[j][k][0]).format('lll');
var f = allArr[j][k][0];
json.push({
"datetime": f
});
if (j == 0) {
if (json[k].datetime === allArr[0][k][0]) {
var newAtt = "water_actual";
var newValue = allArr[0][k][1];
json[k][newAtt] = newValue;
}
}
if (j == 1) {
if (json[k].datetime === allArr[1][k][0]) {
var newAtt = "rainfall_actual";
var newValue = allArr[1][k][1];
json[k][newAtt] = newValue;
}
}if (j == 2) {
if (json[k].datetime == allArr[2][k][0]) {
var newAtt = "forecast_water";
var newValue = allArr[2][k][1];
json[k][newAtt] = newValue;
}
}
}
}
};
});
});
I was able to add a new namewater_actual and its value using if statement. If the datetime from the json object matches to the array value(date and time), I'd like to add it with its specific name as stated above. But I can't seem to make it work.
Here's the fiddle.
If I may provide some general feedback: it's probably good practice to simplify your code to the minimum example that reproduces your problem. Not only can that drastically increase your chances of fixing it yourself, it also increases the odds that you'll get help here.
With that in mind, consider the basic structure of what you're trying here:
var someNames = ["foo", "bar"];
var allTheData = [{
"aardvark": true
}];
$.each(someNames, function (i, name) {
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
allTheData.push(data);
});
});
console.log(allTheData);
Here, $.each iterates through everything in someNames and then proceeds immediately to the console.log statement. For all we know, each individual API call ($.get) could take seconds, or minutes. By this time we've already tried to use the contents of allTheData, which may or may not have been modified.
To avoid this sort of thing in legacy JavaScript we can make use of the callback already provided by $.get:
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
console.log(data);
});
Inside the callback, we know for sure that the API request has already completed (although the above assumes that it succeeded, which is a whole other kettle of fish). This would output the result of each API request as the responses arrive, though not necessarily in the order you'd expect!
JavaScript's asynchronous nature tended to lead in the past to a whole lot of callbacks. With the advent of ES6 we have some more options available to us, especially promises.
I am trying to make a bookmarklet that when clicked will check the URL of the current tab/window to see if it contains 'char1' and/or 'char2' (a given character). If both chars are present it redirects to another URL, for the other two it will append the current URL respectively.
I believe there must be a more elegant way of stating this than the following (which has so far worked perfectly for me) but I don't have great knowledge of Javascript. My (unwieldy & repetitive) working code (apologies):
if (window.location.href.indexOf('char1') != -1 &&
window.location.href.indexOf('char2') != -1)
{
window.location="https://website.com/";
}
else if (window.location.href.indexOf('char1') != -1)
{
window.location.assign(window.location.href += 'append1');
}
else if (window.location.href.indexOf('char2') != -1)
{
window.location.assign(window.location.href += 'append2');
}
Does exactly what I need it to but, well... not very graceful to say the least.
Is there a simpler way to do this, perhaps with vars or a pseudo-object? Or better code?
A (sort-of) refactoring of dthorpe's suggestion:
var hasC1 = window.location.href.indexOf('char1')!=-1
var hasC2 = window.location.href.indexOf('char2')!=-1
var newLoc = hasC1
? hasC2 ? "https://website.com/" : window.location.href+'append1'
: hasC2 ? window.location.href+'append1' : '';
if (newLoc)
window.location = newLoc;
Calling assign is the same as assigning a value to window.location, you were doing both with the addition assignment += operator in the method anyway:
window.location.assign(window.location.href+='append2')
This would actually assign "append2" to the end of window.location.href before calling the assign method, making it redundant.
You could also reduce DOM lookups by setting window.location to a var.
The only reduction I can see is to pull out the redundant indexof calls into vars and then test the vars. It's not going to make any appreciable difference in performance though.
var hasChar1 = window.location.href.indexOf('char1') != -1;
var hasChar2 = window.location.href.indexOf('char2') != -1;
if (hasChar1)
{
if (hasChar2)
{
window.location="https://website.com/";
}
else
{
window.location.assign(window.location.href+='append1');
}
}
else if (hasChar2)
{
window.location.assign(window.location.href+='append2');
}
Kind of extendable code. Am i crazy?
var loc = window.location.href;
var arr = [{
url: "https://website.com/",
chars: ["char1", "char2"]
}, {
url: loc + "append1",
chars: ["char1"]
}, {
url: loc + "append2",
chars: ["char2"]
}];
function containsChars(str, chars)
{
var contains = true;
for(index in chars) {
if(str.indexOf(chars[index]) == -1) {
contains = false;
break;
}
}
return contains;
}
for(index in arr) {
var item = arr[index];
if(containsChars(loc, item.chars)) {
window.location.href = item.url;
break;
}
}
var location =window.location.href
if (location.indexOf('char1')!=-1 && location.indexOf('char2')!=-1)
{window.location="https://website.com/";}
else if (location.href.indexOf('char1')!=-1) {window.location.assign(location+='append1');}
else if (location.indexOf('char2')!=-1) {window.location.assign(location+='append2');}