Evaluate variable in function before transforming function to string - javascript

I'm using pouchDb and to query the database it requires the creation of a map function (which is standard practice for couchDB)
This version is working:
function (doc) {
if (doc.type) {
emit(doc.type)
}
}.toString()
and it results in:
"function mapFunction(doc) {
if (doc.type) {
emit(doc.type);
}
}"
However, I'm trying to change my function call to be more dynamic so I can pass a field through that the map function should be built on. With that in mind, I have a variable called field and I change my map function to this:
var field = '_id'
function (doc) {
if (doc[field]) {
emit(doc[field)
}
}.toString()
the problem is, the string that's generated is like so:
"function mapFunction(doc) {
if (doc[field]) {
emit(doc[field]);
}
}"
but I need to it to be:
"function mapFunction(doc) {
if (doc['_id']) { //or doc._id (I don't mind)
emit(doc['_id']);
}
}"
Is it possible to achieve this?
Edit: Worse case scenario, I write it as a string and do it that way but would prefer to have it as a readable function.

Perhaps a generator that takes a function, a variable name and a value and creates the string you want would do.
Something like
function functionGenerator(func, variable, value){
var r = new RegExp(variable,'gi');
return func.toString().replace(r, value);
}
function mapFunction(doc) {
if (doc[field]) {
emit(doc[field]);
}
}
var map = functionGenerator(mapFunction, 'field','\'_id\'');
console.log(map);

You could define a new method on the Function prototype that performs a toString, but allows to pass a collection of variables in an object format -- where each key is the variable to use. Those variables are injected in the string representation of the function, as var declarations right after the function body opens with a brace.
Each variable gets the JSON representation of its original value. This, of course, has some limitations, as not all values can be represented as JSON (cyclic references, objects with methods, ...etc). But it certainly works with primitive values such as strings:
Function.prototype.toStringWith = function(vars) {
return this.toString().replace(/(\)\s*{)/,
'$1\n var ' + Object.keys(vars)
.map( key => key + ' = ' + JSON.stringify(vars[key]) )
.join(',\n ') + ';');
}
// Demo
var field = '_id'
var s = function mapFunction(doc) {
if (doc[field]) {
emit(doc[field])
}
}.toStringWith({field}); // ES6 shortcut notation
console.log(s);
If you would have more variables that the function needs to "know", like size, weight, brand, then you call .toStringWith({field, size, weight, brand}), ...etc.
NB: solutions that search for the variable name in the function source and replace it with the literal value will need to be careful: the variable name could occur in a quoted string (between single quotes, doubles quotes), or template literals, or be part of a larger name, where it should not be replaced.

I think the easiest solution is a simple regexp.
var field = '_id';
var a = function (doc) {
if (doc[field]) {
emit(doc[field])
}
}.toString();
console.log(a.replace(/field/gi, field));

As I've commented, that's broad because you need to parse and re-generate this stringified function. I can't believe a plugin will force someone to stringify a function.
Since it's broad to do that replacement from field to __id (because of other identifiers, etc.) you can only re-declare this field with its initial value in the stringified function (assign its value at the top).
Not related-advice:
(Remind: var statement declares a variable in the entire scope, so the variable can be assigned before the var statement is present, too.)
//////////////////////////////////////////////
//////////////// References //////////////////
//////////////////////////////////////////////
var _stringify = JSON.stringify
//////////////////////////////////////////////
//////////////// Variables //////////////////
//////////////////////////////////////////////
var field = '__id'
/* Store the variables to be copied in the top here */
var locals = { field: field }
/* String to contain the variables */
var stringified_locals = '',
// thanks to this var length notation
// we'll separate the variables by commas
len = 0
/* Calculate the length of vars */
for (let prop in locals)
++len
/* Also useful for the variable separation */
i = 0; var i
/* Only declare the 'var' statement if there's at least
* ONE var */
if (len)
stringified_locals = 'var '
/* Now generate the string of variables */
for (let prop in locals) {
let value = _stringify(locals[prop])
stringified_locals += prop + ' = ' + value
/* Add comma separator if neccessary */
if (i++ < (len - 1))
stringified_locals += ', '
}
/* And the complete stringified function */
stringified_locals + '\r\n' +
(function (doc) {
if (doc.type) {
emit(doc.type)
}
}).toString()
Got result:
`var field = "__id"
function (doc) {
if (doc.type) {
emit(doc.type)
}
}`

You could do this:
"(function() {\n" +
"var field = " + JSON.stringify(field) + ";\n" +
"return " + mapFunction.toString() + ";" +
"})()"
Caveat: There are rare cases where JSON.stringify doesn't produce valid javascript. I don't know exactly what those cases are or whether it would be possible for a malicious user to take advantage of them in some way. (Do you trust whoever is supplying the value of field?)

Related

Extract variable declarations from JavaScript file

I am trying to create a variable name exchanger. Simple iterating over lines from a JavaScript file and exchange every variable name with a random name from a dictionary i can supply.
I can do that so far, but i am stuck optimizing the search regex for variable keywords.
I first define Keywords which i am looking for:
const VARIABLE_KEYWORDS = ["var", "let", "const"];
Then i iterate over every line from the input file and extract the variable names and store them in an array. Once i have all variable names stored, i make them unique and then simply replace them in the original file with a random word.
That is my extractVariables(line) logic, where the variable line is a string:
function extractVariables(line) {
let lineCnt = 1;
let found_vars = [];
// if the line is not empty, parse it
if (line.trim().length !== 0) {
// find a variable keyword
for (let key of VARIABLE_KEYWORDS) {
// regex to match the variable keyword and its not inside a string definition \"|\' , and is not used as a property name |\.
var re = new RegExp("(?<!\"|\')\\b" + key + "\\b(?!\"|\')", "g");
// while there are variables declared in the line, add them to the variables array
while ((match = re.exec(line)) != null) {
const indexOfKeyword = match.index + key.length;
// get the name after and store it in an array
if (indexOfKeyword > 0) {
let found_var = line.substr(indexOfKeyword).trim().split(' ').shift();
// if a keyword is found, but no variable defined afterwards do not break everything
if(found_var.length !== 0){
found_vars.push(found_var);
}
}
}
}
}
return found_vars;
}
My regex is missing some fixes.
new RegExp("(?<!\"|\')\\b" + key + "\\b(?!\"|\')", "g");
The lookbehind is not supported.
KEYWORDS inside a String like "foo var let me be" are still found, i only fixed it if they are the only word next to a " or '
Scopes {} are ignored
Can someone help me out please?

Update javascript variable using another variable value

I have a situation as follows.
var varable_1=10;
var variable_2=20;
.....
var variable_n=10000;
function update_varable(variable){
....some code.....
}
I need to update each of those variables by calling update_variable('variable_1');update_variable('variable_2')....etc.
Is it possible?
If you want to pass the variables inside the function update_variable then you need to remove the quotes in your example. There is many ways to do it, I post a simple one. You can also pass more than one variable inside the function.
Demo here
var varable_1=10;
var variable_2=20;
var variable_n=10000;
function update_variable(x){
x = 300 //some new value
return x;
}
and the call:
variable_1 = update_variable(varable_1);
( your function name misses an "i" on some lines, it's "update_varable" )
^
missing "i"
If you have to use a string as argument for the update function, you can use eval inside of the function to get the real variable behind the string:
function update(varName) {
eval(varName + " += 1;");
}
I think array is more suitable for the task.
But you can use this code with eval function if your varaibles names are like var1, var2 .. varN:
var var1 = 10;
var var2 = 20;
function update_var(variable) {
return variable += 1;
}
function main() {
for (var i = 1; i < 3; i++) {
eval("var" + i + " = update_var(var" + i + ")");
eval("console.log(var" + i + ");");
}
}
Let's see the facts here:
Your variables have a pattern: variable_x so, for our algorithm it s just a string: 'variable_' + x
All your variables will be attached to an object, wetter it is a declared object, or a global one, for example: a, myVars, or window
Any object in javascript can be accessed using indexers, so myObject.myVar can be also written like myObject['myVar'].
Now let's see the algorithm:
function update(variable, value){
window[variable] = value;
}
You can call it like you wanted:
update('variable_1', 450.25);
function update_varable(variable){
variable += 10;
return variable;
}

Javascript - Iterate the properties of an object and change them

I wish to iterate over an object's properties and change them all to include "" around the value stored in them.
This object is passed to a REST call and the above format must be enforced. I prefer to handle the addition of "" in a central location, rather when assigning the actual values (the code is very complex and long).
I know that you can iterate through the object's properties easily:
$.each(queryOptions, function(obj){console.log(obj)})
However, can I somehow get reference to the actual property and set it from within the iteration?
Input:
queryOptions.value1 = 1234;
queryOptions.value2 = "testing";
queryOptions.value3 = 555;
Desired output:
queryOptions.value1 = "1234";
queryOptions.value2 = ""testing"";
queryOptions.value3 = "555";
Thanks
I agree with Pointy that this seems an odd requirement. But if it's really a requirement:
Using $.each:
$.each(queryOptions, function(key) {
queryOptions[key] = '"' + queryOptions[key] + '"';
});
Or just using JavaScript without any library stuff:
var key;
for (key in queryOptions) {
if (queryOptions.hasOwnProperty(key)) {
queryOptions[key] = '"' + queryOptions[key] + '"';
}
}

Get variable names with JavaScript

I want to create a log function where I can insert variable names like this:
var a = '123',
b = 'abc';
log([a, b]);
And the result should look like this in the console.log
a: 123
b: abc
Get the value of the variable is no problems but how do I get the variable names? The function should be generic so I can't always assume that the scope is window.
so the argument is an array of variables? then no, there is no way to get the original variable name once it is passed that way. in the receiving end, they just look like:
["123","abc"];
and nothing more
you could provide the function the names of the variables and the scope they are in, like:
function log(arr,scope){
for(var i=0;i<arr.length;i++){
console.log(arr[i]+':'scope[arr[i]]);
}
}
however, this runs into the problem if you can give the scope also. there are a lot of issues of what this is in certain areas of code:
for nonstrict functions, this is window
for strict functions, this is undefined
for constructor functions, this is the constructed object
within an object literal, this is the immediate enclosing object
so you can't rely on passing this as a scope. unless you can provide the scope, this is another dead end.
if you pass them as an object, then you can iterate through the object and its "keys" and not the original variable names. however, this is more damage than cure in this case.
I know you want to save some keystrokes. Me too. However, I usually log the variable name and values much like others here have already suggested.
console.log({a:a, b:b});
If you really prefer the format that you already illustrated, then you can do it like this:
function log(o) {
var key;
for (key in o) {
console.log(key + ":", o[key]);
}
}
var a = '1243';
var b = 'qwre';
log({
a:a,
b:b
});
Either way, you'd need to include the variable name in your logging request if you want to see it. Like Gareth said, seeing the variable names from inside the called function is not an option.
Something like this would do what you're looking for:
function log(logDict) {
for (var item in logDict) {
console.log(item + ": " + logDict[item]);
}
}
function logSomeStuff() {
var dict = {};
dict.a = "123";
dict.b = "abc";
log(dict);
}
logSomeStuff();
Don't know if this would really work in JS... but you can use a Object, in which you can store the name and the value:
function MyLogObject(name, value) {
this.name = name;
this.value = value;
}
var log = [];
log.push(new MyLogObject('a', '123'));
log.push(new MyLogObject('b', 'abc'));
for each (var item in log) {
if (item.value != undefined)
alert(item.name + "/" + item.value);
}
Then you can loop thru this Object and you can get the name and the value
You can't access the variable names using an Array. What you could do is use objects or pass the variable names as a String:
var x = 7;
var y = 8;
function logVars(arr){
for(var i = 0; i < arr.length; i++){
alert(arr[i] + " = " + window[arr[i]]);
}
}
logVars(["x","y"]);
I had a somewhat similar problem, but for different reasons.
The best solution I could find was:
MyArray = ["zero","one","two","three","four","five"];
MyArray.name="MyArray";
So if:
x=MyArray.name;
Then:
X=="MyArray"
Like I said, it suited my needs, but not sure HOW this will work for you.
I feel silly that I even needed it, but I did.
test this.
var variableA="valor01"; <br>
var variableB="valor02";
var NamevariableA=eval('("variableA")');<br>
var NamevariableB=eval('("variableB")');<br>
console.log(NamevariableA,NamevariableB);
atte.
Manuel Retamozo Arrué

Creating objects of unknown size NOT using eval

I'm currently using javascript eval() to check and create a multidimensional object that I have no idea of the depth.
Basically, I want to know if there's any way to create this multi-depth object. The object can be as deep as result['one']['two']['three']['four']['five']['six']['seven']. I know there are cases where using eval() is perfectly fine, but I'm also worried about performance. I thought about referencing each depth to a new variable, but I don't know how to do pointers in Javascript
create = function(fields, create_array){
var field;
for (j = 0; j < len; j++){
field = fields.slice(0, j).join('');
if (field){
// is there any way to do this without eval?
eval('if (typeof result' + field + ' == "undefined" || !result' + field + ') result' + field + ' = ' + (create_array?'[]':'{}') + ';');
}
}
}
How about
var deep = { one: { two: { three: { four: { five: { six: { seven: 'peek-a-boo!' }}}}}}};
I don't see what "eval()" has to do with this at all; there's no reason to "initialize" such an object. Just create them.
If you wanted to write a function with an API like you've got (for reasons I don't understand), you could do this:
function create(fields, create_array) {
var rv = create_array ? [] : {}, o = rv;
for (var i = 0; i < fields.length; ++i) {
o = o[fields[i]] = create_array ? [] : {};
}
return rv;
}
There doesn't seem to be any point to the "create_array" flag, since you're presumably always using strings for keys.
Never mind, found my way in. I used a recursive function to ensure that the object was created properly.
create = function(create_array, res, path){
var field = fields.shift();
if (field){
if (typeof res[field] == "undefined" || !res[field]) res[field] = (create_array?[]:{});
path.push('["' + field + '"]');
create(create_array, res[field], path);
}
}
var result = {}, strpath = [], fields[];
create(true, result, strpath);
eval('result' + strpath.join('') + ' = value;');
being variable "field" a variable outside the function, that contained the levels of the object. doing result["field"]["name"]["first"] = value without the ["field"] or ["name"] field existing or defined as an object, would throw an error and stop execution, that's why I'm pre-creating the object variable, either as an array or object.
I couldn't find another option for the second eval() though. There's no way to provide a way to access multiple properties on an object without knowing the depth.

Categories