krishna teja
krishna teja

Reputation: 159

How to do a deep filtering of an object using Javascript

I am having a multi level object of not sure how many levels it can have for example I am having a following object

[{
        "id": "1234",
        "desc": "sample1",
        "items": [{
            "item1": "testItem1",
            "item2": "testItem2"
        }]
    },
    {
        "id": "3456",
        "desc": "sample2",
        "items": [{
            "item1": "testItem12",
            "item2": "testItem23"
        }]
    }
]

How can we do a deep search for a string in pure javascript so that if the search string matches any of the values in the above object should return its respective object level.

for example If I search for "sample" it should return both the objects and if I search for "1234" it should return only one object. Is there a way we can search for the string with out passing the key and value pair we are searching? search string can be any where in the object. please help me with the logic.

lets say if I search with either "sample" or "testItem1" It should return me both the object from the parent level as it matches in both and If I search for "testItem23" then it should return me the complete 2nd object from the parent level.

Upvotes: 6

Views: 7599

Answers (3)

RobG
RobG

Reputation: 147473

If you need to deal with values other than arrays and strings, you'll need to test the values before comparing. You might compare the value passed in so you only test property values with the right type.

This just returns objects that are elements of the original data array. If you want some other object, you'll need to provide the logic.

var arr = [{
    "id": "1234",
    "desc": "sample1",
    "items": [{
      "item1": "testItem1",
      "item2": "testItem2"
    }]
  },
  {
    "id": "3456",
    "desc": "sample2",
    "items": [{
      "item1": "testItem12",
      "item2": "testItem23"
    }]
  }
];

function findInObjArray(array, value) {
  var found = [];

  // Helper to search obj for value
  function findInObj(obj, value) {
    return Object.values(obj).some(
      v =>
        // If v is an object, call recursively
        typeof v == 'object' && v != 'null'? findInObj(v, value) :
        // If string, check if value is part of v
        typeof v == 'string'? v.indexOf(value) >= 0 :
        // Check numbers, make NaN == NaN
        typeof v == 'number'? v === value || isNaN(v) && isNaN(value):
        // Otherwise look for strict equality: null, undefined, function, boolean
        v === value
    );
  };

  array.forEach(function(obj) {
    if (findInObj(obj, value)) found.push(obj);
  })
  return found;
}

console.log(
  findInObjArray(arr, 'testItem23')
);

Upvotes: 1

Akrion
Akrion

Reputation: 18525

Here is another approach which utilizes js only. The idea is to filter the main array and inside for each object to flatten the values via recursion. Then use another filter with indexOf to match the actual property value inside the flattened values array ...

var data = [{ "id": "1234", "desc": "sample1", "items": [{ "item1": "testItem1", "item2": "testItem2", "item3": [{ "item1": "testItemA", "item2": "testItemB" }] }] }, { "id": "3456", "desc": "sample2", "items": [{ "item1": "testItem12", "item2": "testItem23" }] } ]

const flattenValues = (o, values = []) => !(Object.values(o).forEach(x => (Array.isArray(x) || typeof x == 'object') ? [...flattenValues(x, values)] : values.push(x))) && values
const textSearch = (v, d) => d.filter(x => flattenValues(x).filter(y => y.indexOf(v) >= 0).length)

console.log(textSearch('sample', data))
console.log(textSearch('1234', data))
console.log(textSearch('testItem1', data))
console.log(textSearch('testItem12', data))

More details:

The flattenValues function for example would return for the object with id 1234 an array of the flattened values like this for each object:

['1234', 'sample1', 'testItem1', 'testItem2' ...]

Then the textSearch would use filter and inside compare if the filter of the result of:

flattenValues(x).filter(y => y.indexOf(v) >= 0)

returns truthy value

Upvotes: 1

ggorlen
ggorlen

Reputation: 57185

Here's a simple iterative approach using a stack to walk your structure and build the result array:

const find = (needle, haystack) => {
  const result = [];
  const stack = [[haystack, haystack]];

  while (stack.length) {
    const [curr, parent] = stack.pop();

    for (const o of curr) {
      for (const k in o) {
        if (Array.isArray(o[k])) {
          stack.push([o[k], o]);
        } 
        else if (o[k].includes(needle)) {
          result.push("items" in o ? o : parent);
        }
      }
    }
  }

  return result;
};

const data = [{
    "id": "1234",
    "desc": "sample1",
    "items": [{
      "item1": "testItem1",
      "item2": "testItem2"
    }]
  },
  {
    "id": "3456",
    "desc": "sample2",
    "items": [{
      "item1": "testItem12",
      "item2": "testItem23"
    }]
  }
];

console.log(find("sample", data));
console.log(find("1234", data));
console.log(find("testItem12", data));

Upvotes: 4

Related Questions