Converting a string to a javascript associative array - javascript

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.

Related

Access sub-property with generic/dynamic property list [duplicate]

I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!
You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)
Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/
What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }
Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);
Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());
it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}

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.

Parse JSON-like input containing /regexp/ literals

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

Javascript: finding a value from an object

I have the following object:
var stuff = {};
stuff["jar"] = "biscuit";
stuff["cupboard"] = "food";
Iterating through the list with an For i loop and getting the value is easy, but how would I get the key?
for (var i in stuff) {
var key = GET KEY SOMEHOW
var val = stuff[i];
}
The key is i. However, make sure that the key is in your object, not part of the prototype chain.
for (var i in stuff) {
var key = i;
if (stuff.hasOwnProperty(i)) {
var val = stuff[i];
}
}
See also:
https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
var key = i;
In Javascript's for (foo in bar) if foo is the index of an object or array and happens to be a string, it should print or assign the string when called.
You already have it:
for (var key in stuff) {
var val = stuff[key];
}
If you have the value already you may find the key by using this logic :
for (var i=0;i<numKeyValuePairs;i++)
{
if(val==key[i])
{
document.write(key[i];
}
}

Best javascript syntactic sugar

Here are some gems:
Literals:
var obj = {}; // Object literal, equivalent to var obj = new Object();
var arr = []; // Array literal, equivalent to var arr = new Array();
var regex = /something/; // Regular expression literal, equivalent to var regex = new RegExp('something');
Defaults:
arg = arg || 'default'; // if arg evaluates to false, use 'default', which is the same as:
arg = !!arg ? arg : 'default';
Of course we know anonymous functions, but being able to treat them as literals and execute them on the spot (as a closure) is great:
(function() { ... })(); // Creates an anonymous function and executes it
Question: What other great syntactic sugar is available in javascript?
Getting the current datetime as milliseconds:
Date.now()
For example, to time the execution of a section of code:
var start = Date.now();
// some code
alert((Date.now() - start) + " ms elapsed");
Object membership test:
var props = { a: 1, b: 2 };
("a" in props) // true
("b" in props) // true
("c" in props) // false
In Mozilla (and reportedly IE7) you can create an XML constant using:
var xml = <elem></elem>;
You can substitute variables as well:
var elem = "html";
var text = "Some text";
var xml = <{elem}>{text}</{elem}>;
Using anonymous functions and a closure to create a private variable (information hiding) and the associated get/set methods:
var getter, setter;
(function()
{
var _privateVar=123;
getter = function() { return _privateVar; };
setter = function(v) { _privateVar = v; };
})()
Being able to extend native JavaScript types via prototypal inheritance.
String.prototype.isNullOrEmpty = function(input) {
return input === null || input.length === 0;
}
Use === to compare value and type:
var i = 0;
var s = "0";
if (i == s) // true
if (i === s) // false
Multi-line strings:
var str = "This is \
all one \
string.";
Since you cannot indent the subsequent lines without also adding the whitespace into the string, people generally prefer to concatenate with the plus operator. But this does provide a nice here document capability.
Resize the Length of an Array
length property is a not read only.
You can use it to increase or decrease the size of an array.
var myArray = [1,2,3];
myArray.length // 3 elements.
myArray.length = 2; //Deletes the last element.
myArray.length = 20 // Adds 18 elements to the array; the elements have the empty value. A sparse array.
Repeating a string such as "-" a specific number of times by leveraging the join method on an empty array:
var s = new Array(repeat+1).join("-");
Results in "---" when repeat == 3.
Like the default operator, || is the guard operator, &&.
answer = obj && obj.property
as opposed to
if (obj) {
answer = obj.property;
}
else {
answer = null;
}
var tags = {
name: "Jack",
location: "USA"
};
"Name: {name}<br>From {location}".replace(/\{(.*?)\}/gim, function(all, match){
return tags[match];
});
callback for string replace is just useful.
Getters and setters:
function Foo(bar)
{
this._bar = bar;
}
Foo.prototype =
{
get bar()
{
return this._bar;
},
set bar(bar)
{
this._bar = bar.toUpperCase();
}
};
Gives us:
>>> var myFoo = new Foo("bar");
>>> myFoo.bar
"BAR"
>>> myFoo.bar = "Baz";
>>> myFoo.bar
"BAZ"
This isn't a javascript exclusive, but saves like three lines of code:
check ? value1 : value2
A little bit more on levik's example:
var foo = (condition) ? value1 : value2;
The Array#forEach on Javascript 1.6
myArray.forEach(function(element) { alert(element); });
Following obj || {default:true} syntax :
calling your function with this : hello(neededOne && neededTwo && needThree) if one parameter is undefined or false then it will call hello(false), sometimes usefull
In parsing situations with a fixed set of component parts:
var str = "John Doe";
You can assign the results directly into variables, using the "destructuring assignment" synatx:
var [fname, lname] = str.split(" ");
alert(lname + ", " + fname);
Which is a bit more readable than:
var a = str.split(" ");
alert(a[1] + ", " + a[0]);
Alternately:
var [str, fname, lname] = str.match(/(.*) (.*)/);
Note that this is a Javascript 1.7 feature. So that's Mozilla 2.0+ and Chrome 6+ browsers, at this time.
Immediately Invoked Arrow function:
var test = "hello, world!";
(() => test)(); //returns "hello, world!";
I forgot:
(function() { ... }).someMethod(); // Functions as objects
Create an anonymous object literal with simply: ({})
Example: need to know if objects have the valueOf method:
var hasValueOf = !!({}).valueOf
Bonus syntactic sugar: the double-not '!!' for converting pretty much anything into a Boolean very succinctly.
I love being able to eval() a json string and get back a fully populated data structure.
I Hate having to write everything at least twice (once for IE, again for Mozilla).
Assigining the frequently used keywords (or any methods) to the simple variables like ths
var $$ = document.getElementById;
$$('samText');
JavaScript's Date class providing a semi-"Fluent Interface". This makes up for not being able to extract the date portion from a Date class directly:
var today = new Date((new Date()).setHours(0, 0, 0, 0));
It's not a fully Fluent Interface because the following will only give us a numerical value which is not actually a Date object:
var today = new Date().setHours(0, 0, 0, 0);
Default fallback:
var foo = {}; // empty object literal
alert(foo.bar) // will alert "undefined"
alert(foo.bar || "bar"); // will alert the fallback ("bar")
A practical example:
// will result in a type error
if (foo.bar.length === 0)
// with a default fallback you are always sure that the length
// property will be available.
if ((foo.bar || "").length === 0)
Here's one I just discovered: null check before calling function:
a = b && b.length;
This is a shorter equivalent to:
a = b ? b.length : null;
The best part is that you can check a property chain:
a = b && b.c && b.c.length;
I love how simple it is to work with lists:
var numberName = ["zero", "one", "two", "three", "four"][number];
And hashes:
var numberValue = {"zero":0, "one":1, "two":2, "three":3, "four":4}[numberName];
In most other languages this would be quite heavy code. Value defaults are also lovely. For example error code reporting:
var errorDesc = {301: "Moved Permanently",
404: "Resource not found",
503: "Server down"
}[errorNo] || "An unknown error has occurred";
int to string cast
var i = 12;
var s = i+"";
element.innerHTML = ""; // Replaces body of HTML element with an empty string.
A shortcut to delete all child nodes of element.
Convert string to integer defaulting to 0 if imposible,
0 | "3" //result = 3
0 | "some string" -> //result = 0
0 | "0" -> 0 //result = 0
Can be useful in some cases, mostly when 0 is considered as bad result
Template literals
var a = 10;
var b = 20;
var text = `${a} + ${b} = ${a+b}`;
then the text variable will be like below!
10 + 20 = 30

Categories

Resources