Parse JSON-like input containing /regexp/ literals - javascript

In my web app, I would like to accept arbitrary JavaScript objects as GET parameters. So I need to parse location.search in a way similar to eval, but I want to create self-contained objects only (object literals, arrays, regexps and possibly restricted-access functions):
var search =
location.search ?
decodeURIComponent(location.search).substring(1).split('&') :
['boo=alert(1)', 'filter={a: /^t/, b: function(i){return i+1;}}']
;
var params = {};
for (var i = 0, temp; i < search.length; i++){
temp = search[i].split('=');
temp = temp[1] ? temp : [temp[0], null];
params[temp[0]] = saferEval(temp[1]);
};
console.log(params);
I came up with a version of saferEval function that blocks access to global variables, but it does not block access to built-in functions like alert():
var saferEval = function(s) {
'use strict';
var deteleGlobals =
'var ' +
Object.getOwnPropertyNames(window)
.join(',')
.replace(/(?:eval|chrome:[^,]*),/g, '') +
';'
;
try {
return eval(deteleGlobals + '(' + s + ');') || s;
} catch(e) {
return s;
};
};
See my jsFiddle - alert(1) code is executed.
Note that top.location is not accessible to jsFiddle scripts, you have to run the code locally if you want to fiddle with actual query parameters like ?filter={a: /%5Cd+/g}.
I would use JSON, but I need to have regular expressions at arbitrary places inside arrays and objects. I do not send any of these object back to the server, so using eval for this shouldn't harm the security so much...
How can I convert a string (that encodes JavaScript object) into object without giving it access to global namespace and built-in functions?
UPDATE - only useful "arbitrary" objects turned out to be regexp literals...

Per your comment that you'd be interested in seeing a solution that just solves the issue of having regex values in your JSON, then you could encode all regex values as strings in normal JSON like this:
"/this is my regex/"
Then, process the JSON normally into a javascript object and then call this function on it which will recursively walk through all objects and arrays, find all items that are strings, check them for the above format and, if found, convert those items to regular expressions. Here's the code:
function isArray(obj) {
return toString.call(obj) === "[object Array]";
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
var regexTest = /^\/(.*)\/([gimy]*)$/;
function convertRegex(item) {
if (isArray(item)) {
// iterate over array items
for (var i = 0; i < item.length; i++) {
item[i] = convertRegex(item[i]);
}
} else if (isObject(item)) {
for (var prop in item) {
if (item.hasOwnProperty(prop)) {
item[prop] = convertRegex(item[prop]);
}
}
} else if (typeof item === "string") {
var match = item.match(regexTest);
if (match) {
item = new RegExp(match[1], match[2]);
}
}
return item;
}
And a sample usage:
var result = convertRegex(testObj);
Test environment that I stepped through the execution in the debugger: http://jsfiddle.net/jfriend00/bvpAX/

Until there is better solution, I will add alert (and the like) into my list of local variables which would overshadow global/built-in functions within the eval scope:
var deteleGlobals =
'var ' +
Object.getOwnPropertyNames(window)
.join(',')
.replace(/(?:eval|chrome:[^,]*),/g, '') +
',alert,confirm,prompt,setTimeout;'
;
jsFiddle

Related

object properties wont display when concatenated with a string [duplicate]

How do I display the content of a JavaScript object in a string format like when we alert a variable?
The same formatted way I want to display an object.
Use native JSON.stringify method.
Works with nested objects and all major browsers support this method.
str = JSON.stringify(obj);
str = JSON.stringify(obj, null, 4); // (Optional) beautiful indented output.
console.log(str); // Logs output to dev tools console.
alert(str); // Displays output using window.alert()
Link to Mozilla API Reference and other examples.
obj = JSON.parse(str); // Reverses above operation (Just in case if needed.)
Use a custom JSON.stringify replacer if you
encounter this Javascript error
"Uncaught TypeError: Converting circular structure to JSON"
If you want to print the object for debugging purposes, use the code:
var obj = {
prop1: 'prop1Value',
prop2: 'prop2Value',
child: {
childProp1: 'childProp1Value',
},
}
console.log(obj)
will display:
Note: you must only log the object. For example, this won't work:
console.log('My object : ' + obj)
Note ': You can also use a comma in the log method, then the first line of the output will be the string and after that, the object will be rendered:
console.log('My object: ', obj);
var output = '';
for (var property in object) {
output += property + ': ' + object[property]+'; ';
}
alert(output);
console.dir(object):
Displays an interactive listing of the properties of a specified JavaScript object. This listing lets you use disclosure triangles to examine the contents of child objects.
Note that the console.dir() feature is non-standard. See MDN Web Docs
Try this:
console.log(JSON.stringify(obj))
This will print the stringify version of object. So instead of [object] as an output you will get the content of object.
Well, Firefox (thanks to #Bojangles for detailed information) has Object.toSource() method which prints objects as JSON and function(){}.
That's enough for most debugging purposes, I guess.
If you want to use alert, to print your object, you can do this:
alert("myObject is " + myObject.toSource());
It should print each property and its corresponding value in string format.
If you would like to see data in tabular format you can use:
console.table(obj);
Table can be sorted if you click on the table column.
You can also select what columns to show:
console.table(obj, ['firstName', 'lastName']);
You can find more information about console.table here
Function:
var print = function(o){
var str='';
for(var p in o){
if(typeof o[p] == 'string'){
str+= p + ': ' + o[p]+'; </br>';
}else{
str+= p + ': { </br>' + print(o[p]) + '}';
}
}
return str;
}
Usage:
var myObject = {
name: 'Wilson Page',
contact: {
email: 'wilson#hotmail.com',
tel: '123456789'
}
}
$('body').append( print(myObject) );
Example:
http://jsfiddle.net/WilsonPage/6eqMn/
In NodeJS you can print an object by using util.inspect(obj). Be sure to state the depth or you'll only have a shallow print of the object.
Simply use
JSON.stringify(obj)
Example
var args_string = JSON.stringify(obj);
console.log(args_string);
Or
alert(args_string);
Also, note in javascript functions are considered as objects.
As an extra note :
Actually you can assign new property like this and access it console.log or display it in alert
foo.moo = "stackoverflow";
console.log(foo.moo);
alert(foo.moo);
To print the full object with Node.js with colors as a bonus:
console.dir(object, {depth: null, colors: true})
Colors are of course optional, 'depth: null' will print the full object.
The options don't seem to be supported in browsers.
References:
https://developer.mozilla.org/en-US/docs/Web/API/Console/dir
https://nodejs.org/api/console.html#console_console_dir_obj_options
NB:
In these examples, yourObj defines the object you want to examine.
First off my least favorite yet most utilized way of displaying an object:
This is the defacto way of showing the contents of an object
console.log(yourObj)
will produce something like :
I think the best solution is to look through the Objects Keys, and then through the Objects Values if you really want to see what the object holds...
console.log(Object.keys(yourObj));
console.log(Object.values(yourObj));
It will output something like :
(pictured above: the keys/values stored in the object)
There is also this new option if you're using ECMAScript 2016 or newer:
Object.keys(yourObj).forEach(e => console.log(`key=${e} value=${yourObj[e]}`));
This will produce neat output :
The solution mentioned in a previous answer: console.log(yourObj) displays too many parameters and is not the most user friendly way to display the data you want. That is why I recommend logging keys and then values separately.
Next up :
console.table(yourObj)
Someone in an earlier comment suggested this one, however it never worked for me. If it does work for someone else on a different browser or something, then kudos! Ill still put the code here for reference!
Will output something like this to the console :
Here's a way to do it:
console.log("%o", obj);
Use this:
console.log('print object: ' + JSON.stringify(session));
As it was said before best and most simply way i found was
var getPrintObject=function(object)
{
return JSON.stringify(object);
}
(This has been added to my library at GitHub)
Reinventing the wheel here! None of these solutions worked for my situation. So, I quickly doctored up wilsonpage's answer. This one is not for printing to screen (via console, or textfield or whatever). It does work fine in those situations and works just fine as the OP requested, for alert. Many answers here do not address using alert as the OP requested. Anyhow, It is, however, formatted for data transport. This version seems to return a very similar result as toSource(). I've not tested against JSON.stringify, but I assume this is about the same thing. This version is more like a poly-fil so that you can use it in any environment. The result of this function is a valid Javascript object declaration.
I wouldn't doubt if something like this was already on SO somewhere, but it was just shorter to make it than to spend a while searching past answers. And since this question was my top hit on google when I started searching about this; I figured putting it here might help others.
Anyhow, the result from this function will be a string representation of your object, even if your object has embedded objects and arrays, and even if those objects or arrays have even further embedded objects and arrays. (I heard you like to drink? So, I pimped your car with a cooler. And then, I pimped your cooler with a cooler. So, your cooler can drink, while your being cool.)
Arrays are stored with [] instead of {} and thus dont have key/value pairs, just values. Like regular arrays. Therefore, they get created like arrays do.
Also, all string (including key names) are quoted, this is not necessary unless those strings have special characters (like a space or a slash). But, I didn't feel like detecting this just to remove some quotes that would otherwise still work fine.
This resulting string can then be used with eval or just dumping it into a var thru string manipulation. Thus, re-creating your object again, from text.
function ObjToSource(o){
if (!o) return 'null';
var k="",na=typeof(o.length)=="undefined"?1:0,str="";
for(var p in o){
if (na) k = "'"+p+ "':";
if (typeof o[p] == "string") str += k + "'" + o[p]+"',";
else if (typeof o[p] == "object") str += k + ObjToSource(o[p])+",";
else str += k + o[p] + ",";
}
if (na) return "{"+str.slice(0,-1)+"}";
else return "["+str.slice(0,-1)+"]";
}
Let me know if I messed it all up, works fine in my testing. Also, the only way I could think of to detect type array was to check for the presence of length. Because Javascript really stores arrays as objects, I cant actually check for type array (there is no such type!). If anyone else knows a better way, I would love to hear it. Because, if your object also has a property named length then this function will mistakenly treat it as an array.
EDIT: Added check for null valued objects. Thanks Brock Adams
EDIT: Below is the fixed function to be able to print infinitely recursive objects. This does not print the same as toSource from FF because toSource will print the infinite recursion one time, where as, this function will kill it immediately. This function runs slower than the one above, so I'm adding it here instead of editing the above function, as its only needed if you plan to pass objects that link back to themselves, somewhere.
const ObjToSource=(o)=> {
if (!o) return null;
let str="",na=0,k,p;
if (typeof(o) == "object") {
if (!ObjToSource.check) ObjToSource.check = new Array();
for (k=ObjToSource.check.length;na<k;na++) if (ObjToSource.check[na]==o) return '{}';
ObjToSource.check.push(o);
}
k="",na=typeof(o.length)=="undefined"?1:0;
for(p in o){
if (na) k = "'"+p+"':";
if (typeof o[p] == "string") str += k+"'"+o[p]+"',";
else if (typeof o[p] == "object") str += k+ObjToSource(o[p])+",";
else str += k+o[p]+",";
}
if (typeof(o) == "object") ObjToSource.check.pop();
if (na) return "{"+str.slice(0,-1)+"}";
else return "["+str.slice(0,-1)+"]";
}
Test:
var test1 = new Object();
test1.foo = 1;
test1.bar = 2;
var testobject = new Object();
testobject.run = 1;
testobject.fast = null;
testobject.loop = testobject;
testobject.dup = test1;
console.log(ObjToSource(testobject));
console.log(testobject.toSource());
Result:
{'run':1,'fast':null,'loop':{},'dup':{'foo':1,'bar':2}}
({run:1, fast:null, loop:{run:1, fast:null, loop:{}, dup:{foo:1, bar:2}}, dup:{foo:1, bar:2}})
NOTE: Trying to print document.body is a terrible example. For one, FF just prints an empty object string when using toSource. And when using the function above, FF crashes on SecurityError: The operation is insecure.. And Chrome will crash on Uncaught RangeError: Maximum call stack size exceeded. Clearly, document.body was not meant to be converted to string. Because its either too large, or against security policy to access certain properties. Unless, I messed something up here, do tell!
If you would like to print the object of its full length, can use
console.log(require('util').inspect(obj, {showHidden: false, depth: null})
If you want to print the object by converting it to the string then
console.log(JSON.stringify(obj));
I needed a way to recursively print the object, which pagewil's answer provided (Thanks!). I updated it a little bit to include a way to print up to a certain level, and to add spacing so that it is properly indented based on the current level that we are in so that it is more readable.
// Recursive print of object
var print = function( o, maxLevel, level ) {
if ( typeof level == "undefined" ) {
level = 0;
}
if ( typeof level == "undefined" ) {
maxLevel = 0;
}
var str = '';
// Remove this if you don't want the pre tag, but make sure to remove
// the close pre tag on the bottom as well
if ( level == 0 ) {
str = '<pre>';
}
var levelStr = '';
for ( var x = 0; x < level; x++ ) {
levelStr += ' ';
}
if ( maxLevel != 0 && level >= maxLevel ) {
str += levelStr + '...</br>';
return str;
}
for ( var p in o ) {
if ( typeof o[p] == 'string' ) {
str += levelStr +
p + ': ' + o[p] + ' </br>';
} else {
str += levelStr +
p + ': { </br>' + print( o[p], maxLevel, level + 1 ) + levelStr + '}</br>';
}
}
// Remove this if you don't want the pre tag, but make sure to remove
// the open pre tag on the top as well
if ( level == 0 ) {
str += '</pre>';
}
return str;
};
Usage:
var pagewilsObject = {
name: 'Wilson Page',
contact: {
email: 'wilson#hotmail.com',
tel: '123456789'
}
}
// Recursive of whole object
$('body').append( print(pagewilsObject) );
// Recursive of myObject up to 1 level, will only show name
// and that there is a contact object
$('body').append( print(pagewilsObject, 1) );
You can also use ES6 template literal concept to display the content of a JavaScript object in a string format.
alert(`${JSON.stringify(obj)}`);
const obj = {
"name" : "John Doe",
"habbits": "Nothing",
};
alert(`${JSON.stringify(obj)}`);
I always use console.log("object will be: ", obj, obj1).
this way I don't need to do the workaround with stringify with JSON.
All the properties of the object will be expanded nicely.
Another way of displaying objects within the console is with JSON.stringify. Checkout the below example:
var gandalf = {
"real name": "Gandalf",
"age (est)": 11000,
"race": "Maia",
"haveRetirementPlan": true,
"aliases": [
"Greyhame",
"Stormcrow",
"Mithrandir",
"Gandalf the Grey",
"Gandalf the White"
]
};
//to console log object, we cannot use console.log("Object gandalf: " + gandalf);
console.log("Object gandalf: ");
//this will show object gandalf ONLY in Google Chrome NOT in IE
console.log(gandalf);
//this will show object gandalf IN ALL BROWSERS!
console.log(JSON.stringify(gandalf));
//this will show object gandalf IN ALL BROWSERS! with beautiful indent
console.log(JSON.stringify(gandalf, null, 4));
Javascript Function
<script type="text/javascript">
function print_r(theObj){
if(theObj.constructor == Array || theObj.constructor == Object){
document.write("<ul>")
for(var p in theObj){
if(theObj[p].constructor == Array || theObj[p].constructor == Object){
document.write("<li>["+p+"] => "+typeof(theObj)+"</li>");
document.write("<ul>")
print_r(theObj[p]);
document.write("</ul>")
} else {
document.write("<li>["+p+"] => "+theObj[p]+"</li>");
}
}
document.write("</ul>")
}
}
</script>
Printing Object
<script type="text/javascript">
print_r(JAVACRIPT_ARRAY_OR_OBJECT);
</script>
via print_r in Javascript
var list = function(object) {
for(var key in object) {
console.log(key);
}
}
where object is your object
or you can use this in chrome dev tools, "console" tab:
console.log(object);
Assume object obj = {0:'John', 1:'Foo', 2:'Bar'}
Print object's content
for (var i in obj){
console.log(obj[i], i);
}
Console output (Chrome DevTools) :
John 0
Foo 1
Bar 2
Hope that helps!
I prefer using console.table for getting clear object format, so imagine you have this object:
const obj = {name: 'Alireza', family: 'Dezfoolian', gender: 'male', netWorth: "$0"};
And you will you see a neat and readable table like this below:
Circular references solution
To make string without redundant information from object which contains duplicate references (references to same object in many places) including circular references, use JSON.stringify with replacer (presented in snippet) as follows
let s = JSON.stringify(obj, refReplacer(), 4);
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
alert(s);
This solution based on this (more info there) create JSONPath like path for each object value and if same object occurs twice (or more) it uses reference with this path to reference that object e.g. #REF:$.bar.arr[3].foo (where $ means main object) instead 'render' whole object (which is less redundant)
BONUS: inversion
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields');
let obj = parseRefJSON(s);
console.log(obj);
A little helper function I always use in my projects for simple, speedy debugging via the console.
Inspiration taken from Laravel.
/**
* #param variable mixed The var to log to the console
* #param varName string Optional, will appear as a label before the var
*/
function dd(variable, varName) {
var varNameOutput;
varName = varName || '';
varNameOutput = varName ? varName + ':' : '';
console.warn(varNameOutput, variable, ' (' + (typeof variable) + ')');
}
Usage
dd(123.55); outputs:
var obj = {field1: 'xyz', field2: 2016};
dd(obj, 'My Cool Obj');
The console.log() does a great job of debugging objects, but if you are looking to print the object to the page content, here's the simplest way that I've come up with to mimic the functionality of PHP's print_r(). A lot these other answers want to reinvent the wheel, but between JavaScript's JSON.stringify() and HTML's <pre> tag, you get exactly what you are looking for.
var obj = { name: 'The Name', contact: { email: 'thename#gmail.com', tel: '123456789' }};
$('body').append('<pre>'+JSON.stringify(obj, null, 4)+'</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
i used pagewil's print method, and it worked very nicely.
here is my slightly extended version with (sloppy) indents and distinct prop/ob delimiters:
var print = function(obj, delp, delo, ind){
delp = delp!=null ? delp : "\t"; // property delimeter
delo = delo!=null ? delo : "\n"; // object delimeter
ind = ind!=null ? ind : " "; // indent; ind+ind geometric addition not great for deep objects
var str='';
for(var prop in obj){
if(typeof obj[prop] == 'string' || typeof obj[prop] == 'number'){
var q = typeof obj[prop] == 'string' ? "" : ""; // make this "'" to quote strings
str += ind + prop + ': ' + q + obj[prop] + q + '; ' + delp;
}else{
str += ind + prop + ': {'+ delp + print(obj[prop],delp,delo,ind+ind) + ind + '}' + delo;
}
}
return str;
};

What is the simplest way to Join a object as a space separated string in Javascript

All:
I want to build a string from a JS object( like build a toString method but like operator overload), for example:
var info = {name:"username",age:20};
var fmtinfo = info.join("||");
the fmtinfo will be a string with format:
"name(username)||age(20)"
I wonder if anyone can give me a simple way to do that?
Thanks
To avoid any explicit iteration, you can use Object.keys with map to transform each key into the corresponding entry, then simply join them together:
function fancyString(obj) {
return Object.keys(obj).map(function (k) {
return "" + k + "(" + obj[k] + ")";
}).join("||");
}
var foo = {name: "username", age: 20};
console.log(fancyString(foo));
To offer some explanation of how this work:
The call to Object.keys() returns an array of the object's own enumerable keys, effectively combining the for (f in o) and o.hasOwnProperty() checks. Object.keys is relatively well-supported, by everything except IE9 (although polyfills are not complicated).
That array of keys are transformed via Array.prototype.map(), using the desired string formatting. This is pretty simple, but do not that obj[k] will call its .toString() method (if available) to transform it into a string. This allows excellent handling of custom objects, as you can simply define a toString method on each and it will be picked up by the VM. Again, this is supported by IE9 and better, but polyfills are trivial.
Finally, we join the resulting strings with Array.prototype.join(), which takes care of making sure we don't append the separator to the end or anything like that. All browsers support this.
For curiosity, the ES6 equivalent would be:
let fancyString = o => Object.keys(o).map(k => "" + k + "(" + o[k] + ")").join("||");
You can use for..in statement and helper array:
var info = {name:"username",age:20};
var helperArray = [];
for( var key in info ) {
if( info.hasOwnProperty( key ) ) {
helperArray.push( key + '(' + info[key] + ')' );
}
}
alert(helperArray.join('||'));
You could iterate over the object's keys to get both the key names and their corresponding values. Not incredibly elegant but since the desired format of your string is uncommon, it requires a little work.
var info = {name:"username",age:20};
var keys = Object.keys(info);
var returnVal = ""
for(var i = 0; i < keys.length; i++) {
if(returnVal.length > 0)
returnVal += "||";
returnVal += keys[i] + "(" + info[keys[i]] + ")";
}
alert(returnVal)
Here is a jsfiddle of the solution: http://jsfiddle.net/pstricker/ec1oohsk/
It took a while, but I might have got it working as intended with help of two existing Stackoverflow answers related to: Object iteration and checking if JavaScript variable is function type. I hope this helps :-)
Object.prototype.join = function join(param) {
var helperArray = []
for (var key in this) {
//check that function is not printed -> join: function
if (typeof (this[key]) != "function")
helperArray.push(key + "(" + this[key] + ")")
}
return helperArray.join(param);
}
var info = {
name: "username",
age: 20
};
console.log(info.join('||'));
console.log(info.join('<>'));
console.log(info.join('%'));
Object.prototype.fancyString = function(){
var a = []
for(var prop in this) {
if(this.hasOwnProperty(prop)) {
a.push(prop + '(' + this[prop] + ')' );
}
}
return a.join('||');
}
Then you can just do:
var info = {name:"username",age:20};
info.fancyString();
// outputs: "name(username)||age(20)"

Refer to a javascript object by string value - without using eval()

Looked around SO and didn't find anything that seemed to match what I am trying to do..
I am trying to reference an object by a string representation, though everywhere I look I see that using eval() is bad - though can't find a way to do this without using eval()
So my use case:
I have a data attribute on a button;
data-original-data-object="window.app.myData.originalData"
When the button is clicked I need to access the actual object held at window.app.myData.originalData
Now, I know I can do:
var dataObj = eval($(this).data('original-data-object'));
Though is there any other way to do this?
If it helps, the data that is stored at window.app.myData.originalData is a JSON object.
Like this:
var obj = (function(str){
var arr = str.split('.');
if (arr[0] === 'window'){
arr.shift();
}
return arr.reduce(function(a, b){
return a[b];
}, window);
}("window.app.myData.originalData"))
A couple of solutions come to mind. The first solution is hinted at in #CD..'s answer. The second is to restrict that string via a regex to just property names so you can safely use eval.
Traversing the window object to get the value (no eval)
function getValue(s) {
var keys = s.split("."), o = window, key, i, length, undef;
if (keys[0] === "window") {
keys.shift();
}
for (i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (!(key in o) || o[key] === null || o[key] === undef) {
throw new Error("Could not get value of " + s);
}
o = o[key];
}
return o;
}
Restricting the string to valid property names:
function getValue(s) {
var regex = /^[\w$][\w.]+$/, value;
if (regex.test(s)) {
try {
value = eval(s);
}
catch (error) {
throw new Error("Could not get value of " + s + " (" + error.message + ")");
}
}
else {
throw new Error("Could not get value of " + s);
}
return value;
}
To use:
var x = getValue(this.getAttribute("data-original-data-object"));
You want to avoid using eval because it can arbitrarily execute JavaScript that you may or may not have control of. In this particular case, you know the exact kind of string you want. In my opinion, I'd use a regular expression to make sure the string just contains property names separated by dots. Security speaking, there is no difference between these two lines of code:
var x = eval("window.foo");
var x = window.foo;
Provided that you can ensure that the attribute cannot be modified in anyway that can cause harm to the site/project that this is being implemented on, I don't see any problems.
I'm not sure if this will work for your situation, but a simple solution that avoids eval may be to add "window.app.myData.originalData" with its JSON data as the property of an object that will remain in scope.
Something like:
var exampleData = { id:1, content:"..." };
var dataStore = { "window.app.myData.originalData": exampleData };
Then, in your click handler:
var retrievedData = dataStore[$(this).data('original-data-object')]; // uses "window.app.myData.originalData" to retrieve exampleData
In this case, you will need to access the data using bracket notation because of the . character in the property name. This approach should be faster and safer than trying to use eval, however.

Converting a string to a javascript associative array

I have a string
string = "masterkey[key1][key2]";
I want to create an associative array out of that, so that it evaluates to:
{
masterkey: {
key1: {
key2: value
}
}
}
I have tried this:
var fullName = string;
fullName = fullName.replace(/\[/g, '["');
fullName = fullName.replace(/\]/g, '"]');
eval("var "+fullName+";");
But I get the error: missing ; before statement with an arrow pointing to the first bracket in ([) "var masterkey["key1"]["key2"];"
I know that eval() is not good to use, so if you have any suggestions, preferably without using it, I'd really appreciate it!
Not the most beautiful, but it worked for me:
var
path = "masterkey[key1][key2]",
scope = {};
function helper(scope, path, value) {
var path = path.split('['), i = 0, lim = path.length;
for (; i < lim; i += 1) {
path[i] = path[i].replace(/\]/g, '');
if (typeof scope[path[i]] === 'undefined') {
scope[path[i]] = {};
}
if (i === lim - 1) {
scope[path[i]] = value;
}
else {
scope = scope[path[i]];
}
}
}
helper(scope, path, 'somevalue');
console.log(scope);
demo: http://jsfiddle.net/hR8yM/
function parse(s, obj) {
s.match(/\w+/g).reduce(function(o, p) { return o[p] = {} }, obj);
return obj;
}
console.dir(parse("masterkey[key1][key2]", {}))
Now try this
string = "masterkey[key1][key2]";
var fullName = string;
fullName = fullName.replace(/\[/g, '[\'');
fullName = fullName.replace(/\]/g, '\']');
document.write("var "+fullName+";");
1) When using eval, the argument you provide must be valid, complete javascript.
The line
var masterkey["key1"]["key2"];
is not a valid javascript statement.
When assigning a value to a variable, you must use =. Simply concatenating some values on to the end of the variable name will not work.
2) var masterkey = ["key1"]["key2"] doesn't make sense.
This looks like an attempt to assign the "key2" property of the "key1" property of nothing to masterkey.
If you want the result to be like the example object you give, then that is what you need to create. That said, parsing the string properly to create an object is better than using regular expressions to translate it into some script to evaluate.

Best way of basically doing a `where` clause in Javascript?

I'm trying to parse some JSON that is sent to me and it's all in the format of
[{key:value},{key2:value2}, ... ]
What would be the best way to get the value of key2 in this? Is there a way to do it without doing a for loop?
You could use the Select function from the Underscore.js library.
Not really, but it wouldn't be hard to create a function to do that. However, it would indeed involves a for loop.
For the sake of completion, that would be the function:
function selectWhere(data, propertyName) {
for (var i = 0; i < data.length; i++) {
if (data[i][propertyName] !== null) return data[i][propertyName];
}
return null;
}
Usage:
var key2value = selectWhere(data, "key2");
Javascript Array comes with methods that do just what you are asking for - find entries without you having to code a for-loop.
You provide them with the condition that you want. A compact and convenient way to do that is with an arrow (or "lambda") function. In your case, you are looking for array entries that have a specific key, so the arrow function could look something like this:
e => e.hasOwnProperty("key2")
Following the lead of some of the others, let's start with the assumption
var arr = [{key:"value"}, {key2:"value2"}, {key3:"value3"}]
If you expect that at most one member of the array has the key you want, you can use the find() function. It will test each array member until it finds one where your condition is true, and return it. If none are true, you'll get undefined.
var foundentry = arr.find(e => e.hasOwnProperty("key2"))
Either foundentry will be undefined or it will be the {key2:"value2"} that you are looking for, and can extract value2 from it.
If arr can have more than one entry with the key that you are looking for, then instead of find() use filter(). It gives back an array of entries that meet your criteria.
var foundarray = arr.filter(e => e.hasOwnProperty("key2"))
jQuery grep() is a good analog for a Where clause:
var array = [{key:1},{key:2}, {key:3}, {key:4}, {key:5}];
var filtered = jQuery.grep(array, function( item, index ) {
return ( item.key !== 4 && index > 1 );
});
Your filtered array will then contain two elements,
[{key:3}, {key:5}]
You can't do it with an array, but you can make an associative array like object with it. Once you make it, you can use it like hash.
var arr = [{key:value},{key2:value2}, ... ], obj = {};
for (var i = 0, len = arr.length; i < len; i++) {
$.extend(obj, arr[i]);
}
console.log(obj.key2); // value2
Here's an example that prototype's the Array object. Note: this is shown for example - find is not a good name for this function, and this probably will not be needed for all arrays
Instead, consider just using the function definition and creating a function like getObjVal, calling like getObjVal(arr,'propName'), similar to LaurenT's answer.
Given
var arr = [{key:'value'},{key2:'value2'}];
Definition
// for-loop example
Array.prototype.find = function (prop){
for(var i=this.length; i--; )
if (typeof this[i][prop] !== 'undefined')
return this[i][prop];
return undefined;
}
// for-each loop example
Array.prototype.find = function (prop){
for (var i in this)
if ( this.hasOwnProperty(i) && typeof this[i][prop] !== "undefined" )
return this[i][prop];
return undefined;
}
Usage
console.log( arr.find('key2') ); // 'value2'
console.log( arr.find('key3') ); // undefined
Use .filter() method for this object array, for example in your case:
var objArray = [{key:"Hello"},{key2:"Welcome"} ];
var key2Value=objArray.filter(x=>x.key2)[0].key2;
Regex - no for loop:
var key2Val = jsonString.match(/\{key2:[^\}]+(?=\})/)[0].substring("{key2:".length);
Top answer does the job. Here's a one liner version of it using lodash (same as underscore for the most part):
var result = _.filter(data, _.partialRight(_.has, 'key2'));
In lodash, select is just an alias for filter. I pass it the data array filled with objects. I use _.has as the the filter function since it does exactly what we want: check if a property exists.
_.has expects two args:
_.has(object, path)
Since _.has expects two arguments, and I know one of them is always constant (the path argument). I use the _.partialRight function to append the constant key2. _.partialRight returns a new function that expects one argument: the object to inspect. The new function checks if obj.key2 exists.
Heyas. You can use the lodash library's .reduce() or .transform() functions to implement this. Lodash is more modular than underscore (Underscore around 5kb, Lodash around 17kb), but is generally lighter because you only include the specific modules you need
(please see: https://news.ycombinator.com/item?id=9078590 for discussion). For this demonstration I will import the entire module (generally not an issue on the backend):
I wrote these snippets for either scenario which handle both numeric and non-numeric arguments.
https://lodash.com/docs#reduce
https://lodash.com/docs#transform
Pull in lodash:
var _ = require('lodash');
_.reduce() to where clause:
var delim = ' WHERE ', where = _.isEmpty(options) ? '' : _.reduce(options, function(r, v, k) {
var w = r + delim + k + '=' + (_.isNumber(v) ? v : ("'" + v + "'"));
delim = ' AND ';
return w;
}, '');
_.transform() to where clause:
var where = _.isEmpty(options) ? '' : ' WHERE ', delim = '';
_.transform(options, function(r, v, k) {
where = where + delim + k + '=' + (_.isNumber(v) ? v : ("'" + v + "'"));
delim = ' AND ';
});
Hope that helps.
Try this:
var parsedJSON = JSON.parse(stringJSON);
var value = parsedJSON['key2'];

Categories

Resources