Home > other >  Return object based on search
Return object based on search

Time:01-31

I am having an object that looks like this:

const obj = {
   "cat1" : { 
                id: "1", 
                name: "Category1", 
                tiles: [{ tileName: "abc", searchable: true}, { tileName: "def", searchable: true}]
             },
   "cat2" : { 
                id: "2", 
                name: "Category2", 
                tiles: [{ tileName: "ab", searchable: true}, { tileName: "lmn", searchable: true}]
             },
   "cat3" : { 
                id: "3", 
                name: "Category3", 
                tiles: [{ tileName: "pqr", searchable: true}, { tileName: "", searchable: false}]
             }

}

Based on the search input , I need to check if the search item is included in each object basically inside two fields. One is name and the other is tileName inside tile array ( should be searched only if that object has searchable true ). It should be searched across name and tiles array

When search is "ab", the output should be

const obj = {
   "cat1" : { 
                id: "1", 
                name: "Category1", 
                tiles: [{ tileName: "abc", searchable: true}]
             },
   "cat2" : { 
                id: "2", 
                name: "Category2", 
                tiles: [{ tileName: "ab", searchable: true}]
             },
}

Code that I tried

function handleSearch(search)
{
  return Object.values(obj).map((item) => {
    if(item["name"].toLowerCase().includes(item.toLowerCase()))
      return item;
    })
   })
}

CodePudding user response:

I would personally include a generic filterMap() helper that combines the functionalities of both filter() and map()

// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
//     filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
//     //=> [1,9,25]
//
function filterMap(iterable, fn) {
  const filterMapped = [];
  for (const item of iterable) {
    const mapped = fn(item);
    if (mapped === undefined) continue; // skip current iteration
    filterMapped.push(mapped);
  }
  return filterMapped;
}

With the above helper declared you can get the desired functionality with relative ease

function search(searchString, object) {
  const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());

  return Object.fromEntries(
    filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
      // If the category name matches, return the whole category as is,
      // without filtering the tiles.
      if (searchIn(category.name, searchString)) {
        return [key, { ...category, tiles }];
      }

      // If the category name did not match, filter the tiles.
      const matchingTiles = tiles.filter((tile) => (
        tile.searchable && searchIn(tile.tileName, searchString)
      ));

      // If there are one or more matching tiles found, return the
      // category with only the matching tiles.
      if (matchingTiles.length) {
        return [key, { ...category, tiles: matchingTiles }];
      }

      // If neither the category name nor one of the tiles matched,
      // nothing (undefined) is returned, thus the entry is removed
      // from the result.
    })
  );
}

function search(searchString, object) {
  const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());

  return Object.fromEntries(
    filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
      // If the category name matches, return the whole category as is,
      // without filtering the tiles.
      if (searchIn(category.name, searchString)) {
        return [key, { ...category, tiles }];
      }

      // If the category name did not match, filter the tiles.
      const matchingTiles = tiles.filter((tile) => (
        tile.searchable && searchIn(tile.tileName, searchString)
      ));

      // If there are one or more matching tiles found, return the
      // category with only the matching tiles.
      if (matchingTiles.length) {
        return [key, { ...category, tiles: matchingTiles }];
      }

      // If neither the category name nor one of the tiles matched,
      // nothing (undefined) is returned, thus the entry is removed
      // from the result.
    })
  );
}

const obj = {
  "cat1": {
    id: "1",
    name: "Category1",
    tiles: [
      { tileName: "abc", searchable: true },
      { tileName: "def", searchable: true },
    ],
  },
  "cat2": {
    id: "2",
    name: "Category2",
    tiles: [
      { tileName: "ab",  searchable: true },
      { tileName: "lmn", searchable: true },
    ],
  },
  "cat3": {
    id: "3",
    name: "Category3",
    tiles: [
      { tileName: "pqr", searchable: true  },
      { tileName: "",    searchable: false },
    ],
  },
};

console.log('search "ab"', search("ab", obj));
console.log('search "3"', search("3", obj));

// helper

// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
//     filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
//     //=> [1,9,25]
//
function filterMap(iterable, fn) {
  const filterMapped = [];
  for (const item of iterable) {
    const mapped = fn(item);
    if (mapped === undefined) continue; // skip current iteration
    filterMapped.push(mapped);
  }
  return filterMapped;
}

In the above code we use Object.entries() to convert the object to an array. The array is then transformed using our newly defined filterMap() method. Finally the resulting array is transformed back into an object using Object.fromEntries().

The code makes use of destructuring, the spread syntax in object literals, and the property definition shorthand.


For those using TypeScript, filterMap() should be defined like:

function filterMap<A, B>(iterable: Iterable<A>, fn: (item: A) => undefined | B): B[] {
  const filterMapped: B[] = [];
  for (const item of iterable) {
    const mapped = fn(item);
    if (mapped === undefined) continue; // skip current iteration
    filterMapped.push(mapped);
  }
  return filterMapped;
}

The answer might still leave you with:

Not all code paths return a value. (7030)

In which case you must either explicitly return from the callback function.

  // ...

  // If neither the category name nor one of the tiles matched,
  // nothing (undefined) is returned, thus the entry is removed
  // from the result.
  return; // <- explicit return at the end
})

Or alternatively set "noImplicitReturns": false in your TypeScript settings, to allow implicit returns.

CodePudding user response:

const obj = {"cat1":{"id":"1","name":"Category1","tiles":[{"tileName":"abc","searchable":true},{"tileName":"def","searchable":true}]},"cat2":{"id":"2","name":"Category2","tiles":[{"tileName":"ab","searchable":true},{"tileName":"lmn","searchable":true}]},"cat3":{"id":"3","name":"Category3","tiles":[{"tileName":"pqr","searchable":true},{"tileName":"","searchable":false}]}}

function matchTiles({tileName, searchable}, m) {
  return searchable && caseInsensitiveIncludes(tileName, m)
}
function caseInsensitiveIncludes(a,b) {
  return a.toLowerCase().includes(b.toLowerCase())
}
function search(obj, m) {
  return Object.fromEntries(Object.entries(obj)
    .map(([k, {name, tiles, ...rest}])=>[k,
      {
        ...rest,
        name,
        tiles: caseInsensitiveIncludes(name, m) ? tiles : tiles.filter(i=>matchTiles(i,m))
      }
    ]).filter(([k, {tiles}])=>tiles.length))
}

console.log(search(obj, 'ab'))

CodePudding user response:

const obj = {"cat1":{"id":"1","name":"Category1","tiles":[{"tileName":"abc","searchable":true},{"tileName":"def","searchable":true}]},"cat2":{"id":"2","name":"Category2","tiles":[{"tileName":"ab","searchable":true},{"tileName":"lmn","searchable":true}]},"cat3":{"id":"3","name":"Category3","tiles":[{"tileName":"pqr","searchable":true},{"tileName":"","searchable":false}]}}

function search(obj, text) {
  return Object.values(obj)
    .map((item) => ({
      ...item,
      tiles: item.tiles.filter(tile => tile.searchable && tileName.includes(text))
    }))
    .filter(({tiles}) => tiles.length)
}

console.log(search(obj, 'ab'))

  • Related