0

Let us say we have the following array:

const arr = [{
    id: 0,
    title: 'C',
    countries: [{
      val: "1173",
      label: "Nice"
    }, {
      val: "1172",
      label: "(Yeah)"
    }],
    companies: [{
      val: "7346",
      label: "Hello World"
    }]
  },
  {
    id: 1,
    title: 'B',
    countries: [{
      val: "1175",
      label: "Like it"
    }],
    companies: [{
      val: "8294",
      label: "Javascript"
    }]
  },
]

I would like to do a search for all values that are in this array.

This is my approach:

const objFields = ['countries', 'companies'];
const filterBySearchTerm = (arr, filters) => {
  if (!(arr && arr.length)) {
    return [];
  }

  // filters looks like this: {search: 'hello'}

  const sT = filters.search;
  const searchTermVariations = [sT.toLowerCase()];
  console.log(searchTermVariations);

  /* Start transform all values to arrays */
  const clonedArr = [...arr];
  clonedArr.forEach((job) => {
    const objKeys = Object.keys(job);
    objKeys.forEach((key) => {
      const currVal = job[key];
      const currValToUse = Array.isArray(currVal) ? currVal : [currVal];
      // eslint-disable-next-line no-param-reassign
      job[key] = [...currValToUse];
    });
  });
  /* End transform all values to arrays */

  /* Start transform all obj-values to plain strings */
  const results = clonedArr.filter((itm) => {
    const vals = Object.keys(itm).filter((el) => objFields.includes(el));
    vals.forEach((key) => {
      // eslint-disable-next-line no-param-reassign
      itm[key] = itm[key].map((el) => [el.label, el.val]);
    });

    return vals;
  });

  /* End transform all obj-values to plain strings */

  // perform filtering
  console.info('!! results:', results);
  return results;
};

I am at the point to say that I find my approach too ugly right now. Do you have an idea how this can be done shorter and more intelligent ?

4
  • 3
    what is your desired output? Commented May 14, 2020 at 11:01
  • i might have answered something similar before stackoverflow.com/questions/61538865/… Commented May 14, 2020 at 11:02
  • @Yousaf for instance if one inputs "yeah" or "hello" or "world" the first object in the array should stay in the array after filtering. So it is a fulltext search. Commented May 14, 2020 at 11:05
  • @A.L yeah but my values are deeper structured and I have different structure depths. All values in each object should be matched against the searchTerm. Commented May 14, 2020 at 11:06

1 Answer 1

1

If you want to filter the array of objects by any property value, using a substring match, case-insensitive, and recursing into arrays, this does it:

// Checks to see if any object property matches the search value
// (which must be in lower case), or if any of its array properties
// contains an object that matches
function matchesAnyPropValue(obj, search) {
    return Object.values(obj).some(value =>
        Array.isArray(value)
        ? value.some(v => matchesAnyPropValue(v, search))
        : String(value).toLocaleLowerCase().includes(search)
    );
}

// Apply the given filter to the given array    
function applySearch(arr, filters) {
    const search = filters.search.toLocaleLowerCase();
    return arr.filter(entry => matchesAnyPropValue(entry, search));
}

Live Example:

const arr = [{
    id: 0,
    title: 'C',
    countries: [{
      val: "1173",
      label: "Nice"
    }, {
      val: "1172",
      label: "Yeah"
    }],
    companies: [{
      val: "7346",
      label: "Hello World"
    }]
  },
  {
    id: 1,
    title: 'B',
    countries: [{
      val: "1175",
      label: "Like it"
    }],
    companies: [{
      val: "8294",
      label: "Javascript"
    }]
  },
];

function matchesAnyPropValue(obj, search) {
    return Object.values(obj).some(value =>
        Array.isArray(value)
        ? value.some(v => matchesAnyPropValue(v, search))
        : String(value).toLocaleLowerCase().includes(search)
    );
}

function applySearch(arr, filters) {
    const search = filters.search.toLocaleLowerCase();
    return arr.filter(entry => matchesAnyPropValue(entry, search));
}

document.getElementById("search").addEventListener("click", () => {
    const filters = {search: document.getElementById("filter").value};
    const filtered = applySearch(arr, filters);
    console.clear();
    console.log(filtered);
});
.as-console-wrapper {
    max-height: 100% !important;
}
<input type="text" id="filter" value="yeah">
<input type="button" id="search" value="Search">

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much. Great solution. That is what I was looking for.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.