Fillip Peyton
Fillip Peyton

Reputation: 3657

Get property value from deep object

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: "[email protected]",
        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?

Upvotes: 0

Views: 193

Answers (3)

Nicolas Straub
Nicolas Straub

Reputation: 3411

var originalCookieObject = {
    User: "[email protected]",
    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

Upvotes: 1

Felix Kling
Felix Kling

Reputation: 816472

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.

Upvotes: 1

orhanhenrik
orhanhenrik

Reputation: 1415

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.

Upvotes: 1

Related Questions