Targeting (un)known locations in an object with Javascript - 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!"

Related

Looking for matches in different arrays (Google Apps Script)

I have the following script in Google Apps Script:
for(var i=0; i<lastCode; i++) {
var productCode = prodCodesArr[i];
for(var j=0; j<kelliLastCode; j++) {
var kelliProductCode = kelliCodesArr[j];
if(productCode == kelliProductCode) {
Logger.log('match found')
}
}
}
The 2 arrays are created dynamically. So the idea is (and I know there must be MUCH better ways to do this, but I am pretty new to this so bear with me) that I am setting i to the value of the first product code in one array and then looping through the other array whilst storing the product codes in this one to j. Now, I tried logging:
Logger.log(productCode + ' - ' + kelliProductCode);
And this worked and indeed, there were instances where productCode and kelliProduct code matched.
Yet my if statement above does not pick these up.
Again, I'm sure I've botched this entirely but any help would be greatly appreciated...
What's the point of the check? To determine which of your prodCodesArr items are also in kelliCodesArr? Why not parse kelliCodesArr just once, and then use hash lookups instead of array traversal? This will mean that you don't have to use nested for loops, which will scale very poorly as the inner loop size grows. An example (with some checks for assumptions on my part):
function foo() {
const kelliCodes = getKelliCodesArraySomehow();
const productCodes = getProductCodesArraySomehow();
// If these are 2D arrays, note that for `var a = ['help']; var b = ['help'];`
// `a` is never equal to `b` because they are not the exact same object in memory.
if (kelliCodes.length && Array.isArray(kelliCodes[0])) {
throw new TypeError("This SO answer was predicated on `kelliCodes` and `productCodes` being 1D arrays, but they aren't!");
}
const kelliLookup = kelliCodes.reduce(function (obj, kpc, idx) {
if (typeof kpc === 'object') {
console.log({message: "This SO answer assumed kpc was a string", kpc: kpc});
throw new TypeError("You probably want to store a property of this object, not the whole object");
}
obj[kpc] = idx;
return obj;
}, {});
var productsAlsoInKelliCodes = productCodes.filter(function (pc) {
return kelliLookup.hasOwnProperty(pc);
});
productsAlsoInKelliCodes.forEach(function (pc) {
Logger.log("The index of this product code %s in kelliCodes is %s", pc, kelliLookup[pc]);
});
}
If your ___codes arrays are 2D arrays, you should flatten them before comparison, as comparing an Array instance to another Array instance will always return false, even if they contain the same element primitives--they aren't the exact same Array instance:
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
Array#forEach
Array#map
In JS, which is faster: Object's "in" operator or Array's indexof?
Javascript: what lookup is faster: array.indexOf vs object hash?
I'm sure there are more.
Something like this might help you to see what's happening:
function compareA(prodCodesArr,kelliCodesArr) {
var html="";
for(var i=0;i<prodCodesArr.length;i++) {
for(var j=0;j<kelliCodesArr.length;j++) {
if(productCodesArr[i]==kelliCodesArr[j]) {
html+=Utilities.formatString('Matched: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}else{
html+=Utilities.formatString('No-Match: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}
}
}
var userInterface=HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Comparing')
}

Javascript 'First or Default' function for 'associative arrays'/objects

Is there a better way to do this?
I'm storing values in what some would erroneously call an associated array:
The tokens object stores.... tokens and a count of documents using that token on a per-db level.
var tokens = {'db1' : { '654321': { 'docCount': 1 },
'321456': { 'docCount': 2 } },
'db2' : { '999999': { 'docCount': 1 } } };
I can add/remove dbs and tokens and update the docCounts appropriately.
We can assume, due to code omitted for brevity, that if a db exists, a token also exists with a docCount of at least 1.
If a db exists and I need to retrieve ANY of its tokens, what is the best method?
If the dbs held arrays, it would be as easy as tokens['db1'][0]... but I'm not using arrays.
I have something like the following, "inspired" by LINQ (please don't blame LINQ):
// NOTE: default not implemented here
var firstOrDefault = function(obj) {
var thing;
for (var i in obj) {
thing = i;
break;
}
return thing;
};
which would be called as so (simplified for example):
var anyToken;
if (tokens['db1') { anyToken = firstOrDefault(tokens['db1']); }
Generally returning per the above example '654321' (as this is an object, not an array, order is not guaranteed, but either value is acceptable in my code).
Is this a reasonable method to get any value?
Is there a better method?
Should I just suck it up, shove everything into an array, and wrap the storage features that way?
UPDATE: I've removed the default reference, as an unfound item will a perfectly acceptable undefined response:
// NOTE: obj.hasOwnProperty not implemented for brevity
var firstOrAny = function(obj) {
var thing;
for (var i in obj) {
thing = i;
break;
}
return thing;
};
which would be called as so (simplified for example):
var anyToken;
if (tokens['db1') { anyToken = firstOrAny(tokens['db1']); }
Slightly shorter solution:
var firstOrDefault = function(obj, d) {
for (var i in obj)
{
if (obj.hasOwnProperty(i))
{
return obj[i];
}
}
return d;
};
But yes, it is the fastest way to get any (usually first inserted) key from an object.
I also added a hasOwnProperty check to prevent cases where the values are retrieved from the prototype chain.

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();

How can I find the number if I search for “Addendum” using JavaScript?

I have an object like the following:
var RevenueCodes = {
41020: "Addendum",
41040: "Cardiology Assessment",
41060: "Chiropractic Assessment",
41290: "Neurology File Review - CAT",
41240: "Neurology Assessment"
}
How can I find the number if I search for “Addendum” using JavaScript?
You can use for...in to enumerate object properties.
var RevenueCodes = {
41020: "Addendum",
41040: "Cardiology Assessment",
41060: "Chiropractic Assessment",
41290: "Neurology File Review - CAT",
41240: "Neurology Assessment"
};
for (var propertyName in RevenueCodes) {
if (RevenueCodes[propertyName] === "Addendum") {
console.log("property name: %s", propertyName);
break;
}
}
I would have two vars, RevenueByCode and CodeByRevenue, the former being what you have and the latter being the same except with the key/values reversed, so you can get constant time lookup at the expense of having to (possibly) set up the second variable by looping over the first.
You can do
var code;
for (var key in RevenueCodes) {
var val = RevenueCodes[key];
if (val === 'Addendum') code = key;
}
to get the code (you should optimize a bit) and you can also use the same loop structure to setup your other lookup, if you want to do that.
var number;
for(var key in RevenueCodes) { // iterate
if(RevenueCodes.hasOwnProperty(key) && RevenueCodes[key] === "Addendum") {
// if it's not a prototype property and the value is Addendum, store key
// as number and stop the loop
number = key;
break;
}
}
Javascript has direct way of doing this. You need to loop through all the keys, compare the values and then choose the right one.. If you want to do this repeatedly, you need to build the reverse map once and use it...

Easy way to evaluate path-like expressions in 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.

Categories