74

Similar to brnwdrng's question, I'm looking for a way to search through a JSON-like object.
supposing my object's structure is like so:

TestObj = {
    "Categories": [{
        "Products": [{
            "id": "a01",
            "name": "Pine",
            "description": "Short description of pine."
        },
        {
            "id": "a02",
            "name": "Birch",
            "description": "Short description of birch."
        },
        {
            "id": "a03",
            "name": "Poplar",
            "description": "Short description of poplar."
        }],
        "id": "A",
        "title": "Cheap",
        "description": "Short description of category A."
    },
    {
        "Product": [{
            "id": "b01",
            "name": "Maple",
            "description": "Short description of maple."
        },
        {
            "id": "b02",
            "name": "Oak",
            "description": "Short description of oak."
        },
        {
            "id": "b03",
            "name": "Bamboo",
            "description": "Short description of bamboo."
        }],
        "id": "B",
        "title": "Moderate",
        "description": "Short description of category B."
    }]
};

I'd like to get an object with id="A".

I've tried all sort of stuff such as:

$(TestObj.find(":id='A'"))

but nothing seems to work.

Can anyone think of a way of retrieving an item based on some criteria without using 'each'?

Community
  • 1
  • 1
J. Ed
  • 6,692
  • 4
  • 39
  • 55
  • 2
    1. You have a normal JavaScript object, not a JSON-like object. You just use object literal notation to define it and JSON is only a subset of this (but JSON works in a totally different context). http://benalman.com/news/2010/03/theres-no-such-thing-as-a-json/ 2. jQuery works on the DOM not on arbitrary objects. You are using the wrong tool for the job. There is no other way than to loop over the product arrays. – Felix Kling Feb 14 '11 at 12:58

6 Answers6

121

jQuery doesn't work on plain object literals. You can use the below function in a similar way to search all 'id's (or any other property), regardless of its depth in the object:

function getObjects(obj, key, val) {
    var objects = [];
    for (var i in obj) {
        if (!obj.hasOwnProperty(i)) continue;
        if (typeof obj[i] == 'object') {
            objects = objects.concat(getObjects(obj[i], key, val));
        } else if (i == key && obj[key] == val) {
            objects.push(obj);
        }
    }
    return objects;
}

Use like so:

getObjects(TestObj, 'id', 'A'); // Returns an array of matching objects
David Tang
  • 92,262
  • 30
  • 167
  • 149
  • 2
    It is not a JSON object. And this would also mean you loop over arrays via `for...in` which one should avoid... – Felix Kling Feb 14 '11 at 13:02
  • @Felix, yes you would loop over arrays with `for...in`, but when given any arbitrary object, there is no guarantee that any arrays within that are going to be 'true' arrays. Even if you check the constructor, certain methods like slice, etc, there can still be expando properties, so I don't see the point in defining the logic further. – David Tang Feb 14 '11 at 13:11
  • @Box9: Yes, it might not be worth the effort. Just saying that this *could* lead to unexpected results. But detecting arrays should work ok via `Object.prototype.toString.call(obj)` which would give `[object Array]`. – Felix Kling Feb 14 '11 at 13:14
  • 1
    @Felix, I guess it depends on what you want as the expected result for Arrays. Even if an array is created normally (`[]`), you can still add (non-numeric) properties to them, which you may or may not want to iterate over. Other than that, checking `hasOwnProperty()` protects against iterating over properties from the prototype. – David Tang Feb 14 '11 at 13:20
  • 1
    @Box9: True. Seems I focused to much on the `for...in` and arrays that I did not really realize what you are doing with `hasOwnProperty()`... never mind, I take everything back ;) and +1 as redemption :o) – Felix Kling Feb 14 '11 at 13:22
  • Great work... could this be added to make it more portable? replace: `objects = objects.concat(getObjects(obj[i], key, val));` for: `objects = objects.concat(arguments.callee(obj[i], key, val));` – Andres Jun 23 '11 at 14:20
  • @Box9 I just imagined you as Enrique Iglesias singing "I can be your hero baby!" – capdragon Jul 02 '13 at 18:24
  • @Box9: I was trying to find and update using your code. How to do that? http://stackoverflow.com/questions/17988939/find-and-update-in-nested-json-object – Okky Aug 01 '13 at 10:14
  • I can't figure out how to adapt this to return not the matched object but the "object that contains the matched object"... Does anyone know how? – Andre Bulatov Sep 15 '15 at 22:03
  • Looks good but can you please tell me how to make it work for multiple key values search ? – Zeeshan Apr 18 '17 at 08:29
52

The pure javascript solution is better, but a jQuery way would be to use the jQuery grep and/or map methods. Probably not much better than using $.each

jQuery.grep(TestObj, function(obj) {
    return obj.id === "A";
});

or

jQuery.map(TestObj, function(obj) {
    if(obj.id === "A")
         return obj; // or return obj.name, whatever.
});

Returns an array of the matching objects, or of the looked-up values in the case of map. Might be able to do what you want simply using those.

But in this example you'd have to do some recursion, because the data isn't a flat array, and we're accepting arbitrary structures, keys, and values, just like the pure javascript solutions do.

function getObjects(obj, key, val) {
    var retv = [];

    if(jQuery.isPlainObject(obj))
    {
        if(obj[key] === val) // may want to add obj.hasOwnProperty(key) here.
            retv.push(obj);

        var objects = jQuery.grep(obj, function(elem) {
            return (jQuery.isArray(elem) || jQuery.isPlainObject(elem));
        });

        retv.concat(jQuery.map(objects, function(elem){
            return getObjects(elem, key, val);
        }));
    }

    return retv;
}

Essentially the same as Box9's answer, but using the jQuery utility functions where useful.

········

ObjectType
  • 1,159
  • 10
  • 15
davenpcj
  • 12,508
  • 5
  • 40
  • 37
  • 3
    I used this one `jQuery.grep(TestObj, function(obj) { return obj.id === "A"; })[0].PropertyName;` – Luis Robles Feb 24 '12 at 16:59
  • 3
    for those using `$.grep()`, remember that it returns an array. That means if you're looking for a single item, to access it you need to use `$.grep(/* whatever */)[0]` – Jason Jun 19 '12 at 23:40
  • 1
    $.grep() just saved me a freakin' enormous headache. My problem/solution had nothing to do with the OP, but this lets me only query a database once, as opposed to _n_ times. BRAVO! – edwardrbaker Sep 17 '12 at 20:31
  • @davepncj if i comparing the string then string have some difference that one letter is small in object and one letter is capital in compared string. Then how can we ingnor this difference(small and capital letter) – Bhavin Thummar Jan 01 '19 at 09:14
5

This works for me on [{"id":"data"},{"id":"data"}]

function getObjects(obj, key, val) 
{
    var newObj = false; 
    $.each(obj, function()
    {
        var testObject = this; 
        $.each(testObject, function(k,v)
        {
            //alert(k);
            if(val == v && k == key)
            {
                newObj = testObject;
            }
        });
    });

    return newObj;
}
byedissident
  • 181
  • 2
  • 4
3

For one dimension json you can use this:

function exist (json, modulid) {
    var ret = 0;
    $(json).each(function(index, data){
        if(data.modulId == modulid)
            ret++;
    })
    return ret > 0;
}
Raugaral
  • 1,303
  • 13
  • 25
2

You can use JSONPath

Doing something like this:

results = JSONPath(null, TestObj, "$..[?(@.id=='A')]")

Note that JSONPath returns an array of results

(I have not tested the expression "$..[?(@.id=='A')]" btw. Maybe it needs to be fine-tuned with the help of a browser console)

matt
  • 1,046
  • 1
  • 13
  • 26
-4

Another option I wanted to mention, you could convert your data into XML and then use jQuery.find(":id='A'") the way you wanted.

There are jQuery plugins to that effect, like json2xml.

Probably not worth the conversion overhead, but that's a one time cost for static data, so it might be useful.

davenpcj
  • 12,508
  • 5
  • 40
  • 37