4

I've got a mentally taxing problem here, where I've got a JSON object retrieved using a collection in Backbone. This is what the object looks like:

{
    "MatchID": "00000001",
    "Date": "1970-01-01T00:00:00.000Z",
    "OriginalID": "",
    "Stage": {
        "StageNumber": "0",
        "StageType": "Stage Type"
    },
    "Round": {
        "RoundNumber": "0",
        "Name": "Round Name"
    },
    "Leg": "1",
    "HomeTeam": {
        "TeamID": "0",
        "Name": "Home Team Name"
    },
    "AwayTeam": {
        "TeamID": "0",
        "Name": "Away Team Name"
    },
    "Venue": {
        "VenueID": "0",
        "Name": "Venu Name"
    },
    "Referee": null,
}

What I want to do with this data, is filter it based on a particular attribute, such as the Venue.Name or Date attributes (which are different depths into the object, and can be deeper than two levels for some of the other data). I've got the following code inside a Backbone collection to filter and return a new collection with the contents filtered appropriately:

findWhere: function (Attribute, Value)
{
    return new Project.Collections.Fixtures(this.filter(function (fixture)
    {
        return eval('fixture.attributes.' + Attribute) == Value;
    }));
}

This allows me to specify in an attribute which attribute I want to filter by, and what I want it to be equal to, for any depth of object. The problem is, I really don't want to use "eval" to do this, but obviously I can't use "[Attribute]" for something like "AwayTeam.TeamID", as it won't work.

Does anyone know of a method I can use to achieve this functionality without using eval?

4 Answers 4

9

Something like this would let you traverse the hierarchy of objects to find a value:

var x = {
    y: {
        z: 1
    }
};

function findprop(obj, path) {
    var args = path.split('.'), i, l;

    for (i=0, l=args.length; i<l; i++) {
        if (!obj.hasOwnProperty(args[i]))
            return;
        obj = obj[args[i]];
    }

    return obj;
}

findprop(x, 'y.z');

You could add this as a method to your Fixtureobject:

Fixture = Backbone.Model.extend({
    findprop: function(path) {
        var obj = this.attributes,
            args = path.split('.'), 
            i, l;

        for (i=0, l=args.length; i<l; i++) {
            if (!obj.hasOwnProperty(args[i]))
                return;
            obj = obj[ args[i] ];
        }
        return obj;
    }
});

and use it to extract the value

var f = new Fixture();
f.findprop("HomeTeam.TeamID");

The findWhere method could then be rewritten as

findWhere: function (Attribute, Value)
{
    return new Project.Collections.Fixtures(this.filter(function (fixture){
        return fixture.findprop(Attribute) === Value;
    }));
}

And a Fiddle to play with http://jsfiddle.net/nikoshr/wjWVJ/3/

Sign up to request clarification or add additional context in comments.

1 Comment

Just implemented this in my code, works perfectly for 'n' levels deep in my objects. Thanks!
1

Attributes in JavaScript objects can be accessed by square-bracket, string identifiers as well as the standard dot-notation.

In other words, this:

fixture.attributes.something

is the same as this:

fixture.attributes["something"]

You can also pass variable names in to the square brackets, the value of the variable is used as the key to retrieve.

So you can change your code to this:

findWhere: function (Attribute, Value)
{
    return new Project.Collections.Fixtures(this.filter(function (fixture)
    {
        return fixture.attributes[Attribute] === Value;
    }));
}

As you pointed out in the comments, this only handles one level objects and attributes. To get the nested attributes, you'll need to split the "Attribute" variable and loop through the parts. I like @nikoshr's solution for that.

3 Comments

Thanks for the answer, but this would only work for attributes which are one level deep into the object wouldn't it?
yes, you're right. i wasn't paying enough attention. :) i like @nikoshr's solution for nested attributes.
Yes, @nikoshr's is a very interesting solution I wouldn't have thought of on my own.
1

What about using eval() like this:

var myObject = {
  
  first: 'Some',
  last: 'Person',
  
  address: {
    city: 'Melbourne',
    country: 'Australia'
  }

}

var propPath = 'address.city';

var city = eval("myObject."+propPath);

console.log(city); // = Melbourne

Comments

0

I took nikoshr's answer and added some recursive flair to it:

  var findprop = function (obj, path) {
        var args = (typeof path === 'string') ? path.split('.') : path,
            thisProperty = obj[args[0]];
        if (thisProperty === undefined) { return; } //not found

        args.splice(0, 1); //pop this off the array

        if (args.length > 0) { return findprop(thisProperty, args); } //recurse
        else {return thisProperty; }
    };

I'm not sure if there is much benefit to the recursion cpu cycle-wise, but I like recursive functions when they are appropriate

1 Comment

If you had a fiddle to prove this worked I'd vote it up. Too lazy to analyze myself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.