Link to home
Start Free TrialLog in
Avatar of Marco Gasi
Marco GasiFlag for Spain

asked on

Recursively filtering Javascript object

Hi everybody.
I'm still fighting with Javascript - I ususally do it :)
So, now I0m trying to recursively loop through an object to see if any property contains a string. I tried many pieces of code, but here I report two of them. Both give the same error:

snippet 1
                function iterate(obj, token) {
                    var cardsFound = [];
                    Object.keys(obj).forEach(function (key) {
                        if ($(obj[key]).toLocaleString().toLowerCase().indexOf(token.toLowerCase()) > -1) {
                            cardsFound.push(obj);
                        }
                        if (typeof obj[key] === 'object') {
                            iterate(obj[key]);
                        }
                    });
                    return cardsFound;
                }

Open in new window


Snippet 2
                function eachRecursive(obj, token) {
                    var cardsFound = [];
                    for (var k in obj) {
                        if (typeof obj[k] == "object" && obj[k] !== null) {
                            eachRecursive(obj[k]);
                        } else {
                            if (obj[k].toLocaleString().toLowerCase().indexOf(token.toLowerCase()) > -1) {
                                cardsFound.push(obj);
                            }
                        }
                    }
                    return cardsFound;
                }

Open in new window


The error is "TypeError: token is undefined" related to line 4 in the Snippet 1 and line 7 in the Snippet 2.

The calling handler event is as follows:
                $('#searchButton').on('click', function (e) {
                    e.preventDefault();
                    var sSearch = $('#searchbox').val();
                    alert(sSearch);
                    $('#card-wrapper').empty();
                    console.log(cards);
                    var foundCards = [];

                    foundCards = eachRecursive(cards, sSearch);

//                    foundCards = iterate(cards, sSearch);
                    console.log(foundCards);
                });

Open in new window


Any idea?
Thank you so much for any help.
Avatar of leakim971
leakim971
Flag of Guadeloupe image

could you post an object and a string ?
Avatar of Marco Gasi

ASKER

Hi leakim971.
Of course, I can and I do. But I'm trying to build a generic function I can use with whatever json object regardless of its properties' names.

This is the object:
[
  {
    "ref": "1",
    "tableName": "orders",
    "id": "1",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000002",
    "date": "24/06/2019",
    "product": [
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "2"
      },
      {
        "Name": " ANTINORI MARCHESE CUVEE ROYAL 750ML",
        "Qt": " 3"
      },
      {
        "Name": " CAFFE ESPRESSO ITA. 1KG GRANI",
        "Qt": " 1"
      },
      {
        "Name": " CAFFE ILLY CLASSICO 250GR",
        "Qt": " 1"
      }
    ],
    "totalPrice": "153,76 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000002_2406201943.pdf'>PIN00000002_2406201943.pdf</a>"
  },
  {
    "ref": "21",
    "tableName": "orders",
    "id": "21",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000022",
    "date": "26/06/2019",
    "product": [
      {
        "Name": "CAFFE ESPRESSO ITA. 1KG GRANI",
        "Qt": "1"
      }
    ],
    "totalPrice": "8,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000022_2606201922.pdf'>PIN00000022_2606201922.pdf</a>"
  },
  {
    "ref": "22",
    "tableName": "orders",
    "id": "22",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000023",
    "date": "26/06/2019",
    "product": [
      {
        "Name": "CAFFE ESPRESSO ITA. 1KG GRANI",
        "Qt": "2"
      },
      {
        "Name": " ANTINORI CERVARO DELLA SALA 750ML",
        "Qt": " 1"
      }
    ],
    "totalPrice": "66,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000023_2606201948.pdf'>PIN00000023_2606201948.pdf</a>"
  },
  {
    "ref": "23",
    "tableName": "orders",
    "id": "23",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000024",
    "date": "27/06/2019",
    "product": [
      {
        "Name": "CAFFE ESPRESSO ITA. 1KG GRANI",
        "Qt": "1"
      }
    ],
    "totalPrice": "8,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000024_2706201941.pdf'>PIN00000024_2706201941.pdf</a>"
  },
  {
    "ref": "24",
    "tableName": "orders",
    "id": "24",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000025",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "AGROMONTE CILIEGINO 330GR",
        "Qt": "1"
      },
      {
        "Name": " AGROMONTE CILIEGINO BIO 250GR",
        "Qt": " 1"
      },
      {
        "Name": " CIRIO SUPERCONCENTRATO 140",
        "Qt": " 1"
      },
      {
        "Name": " DAVIA pomodorinini gragnano",
        "Qt": " 1"
      }
    ],
    "totalPrice": "5,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000025_3006201940.pdf'>PIN00000025_3006201940.pdf</a>"
  },
  {
    "ref": "25",
    "tableName": "orders",
    "id": "25",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000026",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "ANTINORI MARCHESE CUVEE ROYAL 750ML",
        "Qt": "1"
      },
      {
        "Name": " ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": " 1"
      },
      {
        "Name": " ANTINORI VILLA ANTINORI BIANCO 750ML",
        "Qt": " 1"
      },
      {
        "Name": " ANTINORI VILLA ANTINORI ROSSO 750ML",
        "Qt": " 1"
      }
    ],
    "totalPrice": "86,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000026_3006201904.pdf'>PIN00000026_3006201904.pdf</a>"
  },
  {
    "ref": "26",
    "tableName": "orders",
    "id": "26",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000027",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "CAFFE IULIANO GRAN AROMA 250GR",
        "Qt": "1"
      },
      {
        "Name": " CAFFE IULIANO INTENSO 1KG GRANI",
        "Qt": " 1"
      }
    ],
    "totalPrice": "10,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000027_3006201939.pdf'>PIN00000027_3006201939.pdf</a>"
  },
  {
    "ref": "27",
    "tableName": "orders",
    "id": "27",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000028",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "CAFFE BORBONE MISCELA NOBILE 250GR",
        "Qt": "1"
      }
    ],
    "totalPrice": "2,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000028_3006201953.pdf'>PIN00000028_3006201953.pdf</a>"
  },
  {
    "ref": "28",
    "tableName": "orders",
    "id": "28",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000029",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "1"
      }
    ],
    "totalPrice": "26,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000029_3006201911.pdf'>PIN00000029_3006201911.pdf</a>"
  },
  {
    "ref": "29",
    "tableName": "orders",
    "id": "29",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000030",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "CAFFE LAVAZZA ORO LATTA 250GR",
        "Qt": "1"
      },
      {
        "Name": " CAFFE LAVAZZA ORO 250GR",
        "Qt": " 1"
      }
    ],
    "totalPrice": "11,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000030_3006201908.pdf'>PIN00000030_3006201908.pdf</a>"
  },
  {
    "ref": "30",
    "tableName": "orders",
    "id": "30",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000031",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "CINZANO ASTI 75CL 7%VOL",
        "Qt": "1"
      },
      {
        "Name": " MARSALLA SUP. MARTINEZ SECCO 5 ANNI 18%VOL 750ML",
        "Qt": " 1"
      }
    ],
    "totalPrice": "23,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000031_3006201925.pdf'>PIN00000031_3006201925.pdf</a>"
  },
  {
    "ref": "31",
    "tableName": "orders",
    "id": "31",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000032",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "MUTTI CONCENTRATO VERDURINE 130GR",
        "Qt": "1"
      },
      {
        "Name": " MUTTI CILIEGINI 400GR",
        "Qt": " 1"
      }
    ],
    "totalPrice": "2,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000032_3006201909.pdf'>PIN00000032_3006201909.pdf</a>"
  },
  {
    "ref": "32",
    "tableName": "orders",
    "id": "32",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000033",
    "date": "30/06/2019",
    "product": [
      {
        "Name": "TORMARESCA CALAFURIA 1",
        "Qt": "1"
      },
      {
        "Name": "5LT",
        "Qt": " 1"
      }
    ],
    "totalPrice": "46,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000033_3006201926.pdf'>PIN00000033_3006201926.pdf</a>"
  },
  {
    "ref": "33",
    "tableName": "orders",
    "id": "33",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000034",
    "date": "01/07/2019",
    "product": [
      {
        "Name": "CAFFE ESPRESSO ITA. 1KG GRANI",
        "Qt": "1"
      }
    ],
    "totalPrice": "8,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000034_0107201936.pdf'>PIN00000034_0107201936.pdf</a>"
  },
  {
    "ref": "34",
    "tableName": "orders",
    "id": "34",
    "customer": "Webintenerife",
    "orderNumber": "PIN00000035",
    "date": "01/07/2019",
    "product": [
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "1"
      },
      {
        "Name": " ANTINORI CERVARO DELLA SALA 750ML",
        "Qt": " 1"
      },
      {
        "Name": " ANTINORI MARCHESE CUVEE ROYAL 750ML",
        "Qt": " 1"
      }
    ],
    "totalPrice": "103,00 EUR",
    "document": "<a class='col-light-blue' href='/orders/PIN00000035_0107201905.pdf'>PIN00000035_0107201905.pdf</a>"
  }
]

Open in new window


My goal is to return all object which contains the string 'antinori' in any of its properties; the search should be case insensitive.

Thank you for you help :)
so you want a function returning an array like this one, no matter what object is sent :
[
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "2"
      },
      {
        "Name": " ANTINORI MARCHESE CUVEE ROYAL 750ML",
        "Qt": " 3"
      }
.......
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "1"
      },
      {
        "Name": "ANTINORI CHIANTI CLASSICO 750ML",
        "Qt": "2"
      },
      {
        "Name": " ANTINORI MARCHESE CUVEE ROYAL 750ML",
        "Qt": " 3"
      }
]

Open in new window

No, not exactly. I already have got that. As you can see, the json represent an array of objects (let's call them cards) and each card has a property which is an array of objects.
I want to iterate through the whole collection and if the token is found I want add the card object where the token is to the resulting array of objects.
So in this case the result should be an array of 5 cards with all their properties. I'm trying right now to understand how to get if an object is child of another object in order to differtiate the code in the loop...

EDIT: and I don't understand why token is undefined since it is one function param...
Avatar of Norie
Norie

Marco

Have a look here http://techslides.com/how-to-parse-and-search-json-in-javascript for how to recursively search a JSON object.
Thank you Norie. It looks interesting. I'll work on it to see if I can modify to avoid to pass key because I want a code I can use with any json object without knowing its properties.
I'll let you know results. Thanks for trying to help :)
I don't understand why the token is undefined since it is one function param...

To answer your last question you can see that you're not passing the token to the function so why you expect from it to be defined?

eachRecursive(obj[k]);

Open in new window


The function expects two parameters but receives just the first obj :

eachRecursive(obj, token)

Open in new window

I have changed the function  getObjects as follows:
                function getObjects(obj, val) {
                    var objects = [];
                    for (var i in obj) {
                        if (!obj.hasOwnProperty(i))
                            continue;
                        if (typeof obj[i] === 'object') {
                            objects = objects.concat(getObjects(obj[i], val));
                        } else
                        if (obj[i].toLowerCase().indexOf(val) > -1) {
                            objects.push(obj);
                        } else if (obj[i].toLowerCase().indexOf(val) > -1) {
                            //only add if the object is not already in the array
                            if (objects.lastIndexOf(obj) === -1) {
                                objects.push(obj);
                            }
                        }
                    }
                    return objects;
                }

Open in new window

It works but it adds all sub-objects which contain the string 'antinori'.
Maybe I have to clarify.
I'm listing all orders stored in the table orders. I don't want to use a table (and the fantastic DataTables plugin) because cards are more mobile-friendly. So I have built cards, one card object for each order. Each card has a list of products stored in the array product; Each element in product is an object with a name and a quantity.
Every code I tried until now, if it was working, has given as result an array holding all products objects. I wants instead add the not the product objects but the card object they belong to.
Hope this help you all to help me :)
Hi Zakaria. Oh, you're totally right, within the iteration I forgot to pass the token!!! I'going to fix this immediately and I'll let you know the result. Thank you!
ASKER CERTIFIED SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Julian, my friend, how are you? Going to try that, thank you
It works perfectly. But this way, the user can only filter the results per product name. I'm trying to allow the user to filter products per any properties without specifying it: it could type the client name, or the order number and the code should filter results accordingly...
But you made me make a step forward, thank you so much, Julian, you're great!!!
That was just an illustration - just pass in whatever level you want to search on.

In my example I passed in i.product - but if you want to search the entire item then just pass in i
items.forEach(function(i) {
  // Here we search the entire item
  if (containsWord(i, 'antinori')) {
    console.log(i, 'contains the word');
  }
});

Open in new window

Fantastic, Julian, you solved my problem!. Thank you very much.
And thank you all for trying to help me :)
Thank you all, guys! But Julian code has solved my issue perfectly.
You are welcome.