Easy way to evaluate path-like expressions in Javascript? - javascript

If I have a JavaScript object such as:
var x = {foo: 42, bar: {fubar: true}}
then I can get the value true with var flag = x.bar.fubar. I'd like to be able to separate out and store the path "bar.fubar", then evaluate it dynamically. Something like:
var paths = ["bar.fubar", ...];
...
var flag = evalPath( x, paths[0] );
Obviously I could write a simple parser and evaluator for a basic path expression grammar. But under DRY principles I wonder if there's already an existing way to do something like evalPath built-in to JavaScript, or a small library that would do the job? I also anticipate needing array indexes in the path expression in future.
Update Just to be clear, I'm not asking for code samples - my question is whether there's existing code (built-in or library) I can re-use. Thanks to the contributors below for suggestions of code samples anyway! Note that none of them handle the array index requirement.

Doing a quick search, I came across JSONPath. Haven't used it at all, but it looks like it might do what you want it to.
Example usage:
var x = {foo: 42, bar: {fubar: true}}
var res1 = jsonPath(x, "$.bar.fubar"); // Array containing fubar's value

Why not try something like
function evalPath(obj, path)
{
var rtnValue = obj;
// Split our path into an array we can iterate over
var path = path.split(".");
for (var i = 0, max=path.length; i < max; i++)
{
// If setting current path to return value fails, set to null and break
if (typeof (rtnValue = rtnValue[path[i]]) == "undefined")
{
rtnValue = null;
break;
}
}
// Return the final path value, or null if it failed
return rtnValue;
}
Not tested, but it should work fairly well. Like XPath, it will return null if it can't find what it's looking for.

JavaScript provides eval, but I don't recommend it.

like
function locate(obj, path) {
var p = path.split("."), a = p.shift();
if(a in obj)
return p.length ? locate(obj[a], p.join(".")) : obj[a];
return undefined;
}
locate(x, "bar.fubar")
this works on the right only, of course

You could try something like this. I can't really think of a situation where it would be appropriate to store paths this way though.
function evalPath(obj, path) {
var pathLevels = path.split('.');
var result = obj;
for (var i = 0; i < pathLevels.length; i++) {
try {
result = result[pathLevels[i]];
}
catch (e) {
alert('Property not found:' + pathLevels[i]);
}
}
return result;
}
The alert is really only there for debugging purposes. You may want to return null or something.

How about:
evalPath = function(obj, path) {
if (path[0] === "[") {
return eval("obj" + path);
} else {
return eval("obj." + path);
}
};
This has the advantage that it works for arbitrary strings:
evalPath([1,2,3], "[0]"); => 1
evalPath({a:{b:7}}, "a.b"); => 7
This, of course, only works if you really trust your input.

Related

Best way to re-format JS code snipet

I wanted to reformat below code in order to keep as minimum as possible Any suggestion to re-format below code and use it as one single method.
function Cookie_Exist(cookieName) {
var all_cookies = document.cookie.split(';');
for (i = 0; i < all_cookies.length; i++) {
var temp_cookie = all_cookies[i].split('=');
var cookie_name = temp_cookie[0].replace(/^\s+|\s+$/g, '');
if (cookie_name === cookieName) {
return true;
}
}
return false;
}
function Get_Cookie(cookieName) {
var all_cookies = document.cookie.split(';');
for (i = 0; i < all_cookies.length; i++) {
var temp_cookie = all_cookies[i].split('=');
var cookie_name = temp_cookie[0].replace(/^\s+|\s+$/g, '');
if (cookie_name === cookieName) {
return temp_cookie[1];
}
}
return null;
}
Instead of reparsing the cookies everytime one could do that once and build up a Map:
const cookies = new Map(document.cookie.split(";").map(pair => pair.split("=")));
Then its as simple as
cookies.get("name");
or
cookies.has("name")
If you had to keep the function Cookie_Exist rather than #Jonas's method, you would do well to use the array methods rather than a for loop. The abstraction can make code shorter and clearer:
function Cookie_Exist(cookieName) {
const allCookies = document.cookie.split(';');
return allCookies.includes(cookieStr => {
const thisCookieName = cookieStr.split('=')[0].replace(/^\s+|\s+$/g, '');
return cookieName === thisCookieName;
});
}
You don't need the Cookie_Exist() function at all, it does the same as the Get_Cookie() function. Use the Get_Cookie() everywhere instead of Cookie_Exist() and check your result. If it is false, then the cookie does not exist. As simple as that.
You may want to clarify what you mean by 'as minimum as possible.' If you're referring to being efficient, you definitely should go the route suggested by Jonas W.
If you're referring to reducing line count? His answer also is a good one, but I would recommend not focusing too much on line counts. Readability should be your number one goal. In other words, what you should be trying to keep to a minimum is the number of mental hoops a human reader of your code must jump through to understand what is going on.
Also, the regex you're doing to strip whitespace can be replaced with String.prototype.trim() which exists for this purpose.
function parseCookie(cookie) {
let { name, value } = cookie.split('=');
return [ name.trim(), value ];
}
function parseCookies() {
let cookies = document.cookie.split(';')
return new Map(cookies.map(parseCookie))
}
// Usage:
const cookieMap = parseCookies();
cookieMap.has('name');
cookieMap.get('name'):

How do I change the Array in place when prototyping

I'm writing a custom sort function that I'm prototyping into Array. (PLEASE don't post answers explaining to me how I shouldn't bother prototyping into Array for whatever reason you feel prototyping into Array isn't a good idea).
so, my method looks like this:
//method
Array.prototype.mySort = function(memberName, ascOrDesc){
var labelRow = this.shift();
var ret = this.sort((function (a,b){
if(ascOrDesc > 0)
return (a[memberName] > b[memberName])?1:-1;
return (a[memberName] < b[memberName])?1:-1;
}));
ret.unshift(labelRow)
return ret;
}
Notice how this.shift() will affect the Array IN PLACE.
However, I'm not clear on how this is accomplished. If I wanted to write my own myShift method, at some point I'd need to say something to the effect of
this = this.myShift();
which is obviously illegal.
So, I'm trying to understand how shift() gets access to the array's members and is able to remove the first one in-place. And if I'm allowed to do something analogous, or if this is somehow baked in and not available to me to use.
You can access the array using this inside the method.
You can for example implement the shift method as:
Array.prototype.myShift = function() {
if (this.length == 0) return null;
var result = this[0];
for (var i = 1; i < this.length; i++) {
this[i-1] = this[i];
}
this.length--;
return result;
};
The problem is that you can't assign to this. This means you can't do things like this:
Array.prototype.myShift = function() {
this = this.slice(1);
};
This is because Array.prototype.slice returns a new array and does not modify the old array. Other methods, however, such as Array.prototype.splice, do modify the old array. So you can do something like this:
Array.prototype.myShift = function() {
return this.splice(0, 1)[0];
};
This will have exactly the same behaviour as the standard Array.prototype.shift method. It modifies the current array, so you can do this:
var labelRow = this.myShift();

Jasmine expect logic (expect A OR B)

I need to set the test to succeed if one of the two expectations is met:
expect(mySpy.mostRecentCall.args[0]).toEqual(jasmine.any(Number));
expect(mySpy.mostRecentCall.args[0]).toEqual(false);
I expected it to look like this:
expect(mySpy.mostRecentCall.args[0]).toEqual(jasmine.any(Number)).or.toEqual(false);
Is there anything I missed in the docs or do I have to write my own matcher?
Add multiple comparable strings into an array and then compare. Reverse the order of comparison.
expect(["New", "In Progress"]).toContain(Status);
This is an old question, but in case anyone is still looking I have another answer.
How about building the logical OR expression and just expecting that? Like this:
var argIsANumber = !isNaN(mySpy.mostRecentCall.args[0]);
var argIsBooleanFalse = (mySpy.mostRecentCall.args[0] === false);
expect( argIsANumber || argIsBooleanFalse ).toBe(true);
This way, you can explicitly test/expect the OR condition, and you just need to use Jasmine to test for a Boolean match/mismatch. Will work in Jasmine 1 or Jasmine 2 :)
Note: This solution contains syntax for versions prior to Jasmine v2.0.
For more information on custom matchers now, see: https://jasmine.github.io/2.0/custom_matcher.html
Matchers.js works with a single 'result modifier' only - not:
core/Spec.js:
jasmine.Spec.prototype.expect = function(actual) {
var positive = new (this.getMatchersClass_())(this.env, actual, this);
positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
return positive;
core/Matchers.js:
jasmine.Matchers = function(env, actual, spec, opt_isNot) {
...
this.isNot = opt_isNot || false;
}
...
jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
return function() {
...
if (this.isNot) {
result = !result;
}
}
}
So it looks like you indeed need to write your own matcher (from within a before or it bloc for correct this). For example:
this.addMatchers({
toBeAnyOf: function(expecteds) {
var result = false;
for (var i = 0, l = expecteds.length; i < l; i++) {
if (this.actual === expecteds[i]) {
result = true;
break;
}
}
return result;
}
});
You can take the comparison out of the expect statement to gain full use of comparison operators.
let expectResult = (typeof(await varA) == "number" || typeof(await varA) == "object" );
expect (expectResult).toBe(true);

JavaScript/jQuery equivalent of LINQ Any()

Is there an equivalent of IEnumerable.Any(Predicate<T>) in JavaScript or jQuery?
I am validating a list of items, and want to break early if error is detected. I could do it using $.each, but I need to use an external flag to see if the item was actually found:
var found = false;
$.each(array, function(i) {
if (notValid(array[i])) {
found = true;
}
return !found;
});
What would be a better way? I don't like using plain for with JavaScript arrays because it iterates over all of its members, not just values.
These days you could actually use Array.prototype.some (specced in ES5) to get the same effect:
array.some(function(item) {
return notValid(item);
});
You could use variant of jQuery is function which accepts a predicate:
$(array).is(function(index) {
return notValid(this);
});
Xion's answer is correct. To expand upon his answer:
jQuery's .is(function) has the same behavior as .NET's IEnumerable.Any(Predicate<T>).
From http://docs.jquery.com/is:
Checks the current selection against an expression and returns true, if at least one element of the selection fits the given expression.
You should use an ordinary for loop (not for ... in), which will only loop through array elements.
You might use array.filter (IE 9+ see link below for more detail)
[].filter(function(){ return true|false ;}).length > 0;
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I would suggest that you try the JavaScript for in loop. However, be aware that the syntax is quite different than what you get with a .net IEnumerable. Here is a small illustrative code sample.
var names = ['Alice','Bob','Charlie','David'];
for (x in names)
{
var name = names[x];
alert('Hello, ' + name);
}
var cards = { HoleCard: 'Ace of Spades', VisibleCard='Five of Hearts' };
for (x in cards)
{
var position = x;
var card = card[x];
alert('I have a card: ' + position + ': ' + card);
}
I suggest you to use the $.grep() method. It's very close to IEnumerable.Any(Predicate<T>):
$.grep(array, function(n, i) {
return (n == 5);
});
Here a working sample to you: http://jsfiddle.net/ErickPetru/BYjcu/.
2021 Update
This answer was posted more than 10 years ago, so it's important to highlight that:
When it was published, it was a solution that made total sense, since there was nothing native to JavaScript to solve this problem with a single function call at that time;
The original question has the jQuery tag, so a jQuery-based answer is not only expected, it's a must. Down voting because of that doesn't makes sense at all.
JavaScript world evolved a lot since then, so if you aren't stuck with jQuery, please use a more updated solution! This one is here for historical purposes, and to be kept as reference for old needs that maybe someone still find useful when working with legacy code.
Necromancing.
If you cannot use array.some, you can create your own function in TypeScript:
interface selectorCallback_t<TSource>
{
(item: TSource): boolean;
}
function Any<TSource>(source: TSource[], predicate: selectorCallback_t<TSource> )
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (let i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
} // End Function Any
Which transpiles down to
function Any(source, predicate)
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (var i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
}
Usage:
var names = ['Alice','Bob','Charlie','David'];
Any(names, x => x === 'Alice');

Targeting (un)known locations in an object with Javascript

I've had trouble with people jumping to conclusions about what I need here, so please read this and think about it before answering.
Here is the case:
You have an incoming object. You do not know the structure of this object. You do however have a "target" to something in the object. So let's pretend there is myObject, and you have some kind of target defined like an array of association levels:
var objectTarget = [ 'firstLevel', 'secondLevel' ,'targetProperty' ];
Now the incoming myObject looks like this:
{
firstLevel: {
secondLevel: {
targetProperty: "The goods!"
}
}
}
But as stated before, you don't know the structure. All you know is what is in the objectTarget array.
My problem is being able to address an arbitrary location within an object based solely off a target. If I knew that the target would always be three levels deep, then I could simply reference it like this:
myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]];
However, because I cannot be sure of the number of level depth, this is not adequate. The only way I have been able to accomplish this task is choose a maximum number of reasonable levels, and then switch on it. Like so:
switch ( objectTarget.length) {
case 1:
var result = myObject[objectTarget[1]];
break;
case 2:
var result = myObject[objectTarget[1]][objectTarget[2]];
break;
case 3:
var result = myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]];
break;
case 4:
var result = myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]][objectTarget[1]];
break;
}
..etc
This is obviously extremely messy, and not the optimal solution.
Does this properly explain my problem? Is there a cleaner manner in which to accomplish this?
Thank you in advance for any advice you can provide.
An attempt off the top of my head:
function findTarget(obj, targets) {
for(var i = 0; i < targets.length; i++) {
var prop = targets[i];
if(obj[prop] != undefined) {
obj = obj[prop];
} else {
return undefined; // Whatever you want when the target does not exist
// or, if it's useful to you
return obj; // Maximum reachable target
}
}
return obj;
}
var target = findTarget(incoming, ['level1', 'level2', ...]);
if(target == undefined) {
// couldn't traverse the entire target list...
}
Another approach if you can use (or include for IE) the reduce Array method:
function getTarget(obj, target) {
return target.reduce(function(a, b) { return a && a[b]; }, obj);
}
// usage (assuming that `myObject` is your sample object):
var target = ['firstLevel', 'secondLevel' ,'targetProperty'];
getTarget(myObject, target); // "The goods!"

Categories