简体   繁体   中英

Multiple level attribute retrieval using array notation from a JSON object

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?

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 Fixture object:

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/

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.

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

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 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM