0

My goal: create a way to get a property's value from a multi-level javascript object.

For example, here is an object I'd like to get a property's value from:

var originalCookieObject = {
        User: "notmyemail@gmail.com",
        GridStates: {
            gridName : {
                "page": 1,
                "pageSize": 50,
                "sort": [
                            {
                                field: "CommonName",
                                dir: "desc"
                            }
                        ]
            }
        },
        ProtectionState: {
            SelectedRow: "string",
            PanelsOpen: ['string', 'string']
        }
    }

And here is how I might retrieve the property's value:

getProperty(originalCookieObject, 'gridName');

I've tried the following recursive function so far:

function traverse(toTraverse, findKey) {
    if( typeof toTraverse == "object" ) {
        var foundValue = toTraverse[findKey];
        if (foundValue){ return foundValue; }

        $.each(toTraverse, function(k,v) {
            // k is either an array index or object key
            traverse(v, findKey);
        });
    }
}
var objectValue = traverse(originalCookieObject, 'gridName');
console.log(objectValue);

My issue is when I try to go more than one level deep, that current traverse() call only returns the property value to the parent traverse() call.

My questions:

  1. How could I make a child traverse() call return the property value to the parent traverse() function?
  2. Is there a better way to do this?
Fillip Peyton
  • 3,637
  • 2
  • 32
  • 60

3 Answers3

1
var originalCookieObject = {
    User: "notmyemail@gmail.com",
    GridStates: {
        gridName : {
            "page": 1,
            "pageSize": 50,
            "sort": [
                {
                    field: "CommonName",
                    dir: "desc"
                }
            ]
        }
    },
    ProtectionState: {
        SelectedRow: "string",
        PanelsOpen: ['string', 'string']
    }
}

function traverse(toTraverse, findKey) {
    if( typeof toTraverse == "object" ) {
        var foundValue = toTraverse[findKey];
        if (foundValue){ return foundValue; }

        $.each(toTraverse, function(k,v) {
            // k is either an array index or object key
            foundValue = traverse(v, findKey) || foundValue;
        });
        return foundValue;
    }
}

var objectValue = traverse(originalCookieObject, 'SelectedRow');
console.log(objectValue);

you were almost there, just needed to assign the result of the nested traverse call to a var and then return it at the end of the function

updated jsfiddle

Fillip Peyton
  • 3,637
  • 2
  • 32
  • 60
Nicolas Straub
  • 3,381
  • 6
  • 21
  • 42
1

You need to check if there is a return value and then store it in a variable:

function traverse(toTraverse, findKey) {
    if( typeof toTraverse == "object" ) {
        var foundValue = toTraverse[findKey];
        if (foundValue){ return foundValue; }
        var returnVal;
        $.each(toTraverse, function(k,v) {
            // k is either an array index or object key
           returnVal = traverse(v, findKey) || returnVal; //set/overwrite the returnVal if there is one
        });
        return returnVal; //return the value to parent, parent will return it further
    }
}
var objectValue = traverse(originalCookieObject, 'gridName');
console.log(objectValue);

So you always return a value, either undefined or the value itself if it was found. This will keep going until it returns to the top. An actual value will always be used over undefined.

orhanhenrik
  • 1,407
  • 8
  • 11
1

How could I make a child traverse() call return the property value to the parent traverse() function?

By storing the return value of the "child" call in a variable and return it.

Is there a better way to do this?

You definitely have to traverse the structure recursively. Using for...in would make it a tiny bit easier since you could directly return from the loop body and don't have to use an intermediate variable.

However, the two issues with your implementation are:

  1. It fails if the searched key has a falsy value (because of if (foundValue){ ... });
  2. You keep looking even after you found the value (well, in your case you don't even know when you found the value, but the other answers have that problem).
  3. If the data structure contains multiple keys with the same name, you are only getting the first one. Whether this is an issue or not depends on the data you are working with.

The following solution takes care of 1. and 2.:

function traverse(toTraverse, findKey) {
    if( typeof toTraverse == "object" ) {
        if (toTraverse.hasOwnPropert(findKey)) { // does object have this property?
            return toTraverse[findKey];
        }
        var foundValue;

        $.each(toTraverse, function(k,v) {
            var value = traverse(v, findKey);
            if (typeof value !== 'undefined') {
                foundValue = value;
                return false; // stop looking
            }
        });

        return foundValue;
    }
}

See jQuery.each for more information.

Caveat: This solution still doesn't treat properties with value undefined properly (e.g. {foo: undefined}), but that should OK for most cases.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thanks for the comprehensive answer! (and for answering both of my questions). This all makes total sense to me, minus one thing: the `typeof value !== 'undefined'` conditional. What is this conditionals purpose? I'm not seeing it. – Fillip Peyton Oct 10 '14 at 20:46
  • Similar to the `toTraverse.hasOwnPropert(findKey)` check, I want to handle falsy values properly. Only when `traverse` returns a value that is not `undefined` we know that we found it. – Felix Kling Oct 10 '14 at 20:51
  • Makes sense. Thanks for your help! Now I'm on my way to use a dot deliminated `findKey` to traverse deeper :) – Fillip Peyton Oct 10 '14 at 20:57