Search code examples
javascriptarraystypescriptfiltere-commerce

Using JS, how to implement an e-commerce style faceted search, with an array of possible criteria which are themselves nested values


Something I often see in e-commerce websites is a sidebar with checkboxes for various filtering values. Many of these can be checked, and adding new values further narrows down your search.

I want to implement something similar for metadata pertaining to digital media. I have an array of possible filters, each of which is an object {nameOfProperty: ['Value1', 'Value2']}. The idea is to return results that satisfy ALL of the criteria in an array of attributes {value: 'Value1', trait_type: 'NameOfProperty'}.

See example:

const arr = [
  { metadata: { json: { 
    attributes: [
      { value: "Copycat Fridge Artist", trait_type: "Artist" },
      { value: "Fridges Vol. 2", trait_type: "Playlist" },
      { value: "Fridges Playlist", trait_type: "Playlist" },
      { value: "Image", trait_type: "Artwork Type" }
    ]
  },

  { metadata: { json: { 
    attributes: [
      { value: "Copycat Fridge Artist", trait_type: "Artist" },
      { value: "Fridges Vol. 1", trait_type: "Collection" },
      { value: "Videos Playlist", trait_type: "Playlist" },
      { value: "Video", trait_type: "Artwork Type" }
    ]
  },

  { metadata: { json: { 
    attributes: [
      { value: "Cool Original Artist", trait_type: "Artist" },
      { value: "Fridges Vol. 1", trait_type: "Collection" },
      { value: "Videos Playlist", trait_type: "Playlist" },
      { value: "Video", trait_type: "Artwork Type" }
    ]
  }
];

and an array of filters

filters: [{Artwork Type: ['Image', 'Video']},
          {Playlist: ['Videos Playlist']},
          {Artist: ['Cool Original Artist']}]

In this scenario, I want to see images or videos, only from the Videos Playlist and then only ones by Cool Original Artist. The filter should return arr[2].

The filters can be many and varied, and not every item in the array has the name number of attributes.

If there is a name for this kind of search/filter I am not aware of it, so apologies if this has been asked before. It's something I've encountered many times, but never tried to code myself, and I'm struggling!


Solution

  • In general I filter the arr values in a reducer.

    const arr = [
      { metadata: { json: { 
        attributes: [
          { value: "Copycat Fridge Artist", trait_type: "Artist" },
          { value: "Fridges Vol. 2", trait_type: "Playlist" },
          { value: "Fridges Playlist", trait_type: "Playlist" },
          { value: "Image", trait_type: "Artwork Type" }
        ]
      }}},
    
      { metadata: { json: { 
        attributes: [
          { value: "Copycat Fridge Artist", trait_type: "Artist" },
          { value: "Fridges Vol. 1", trait_type: "Collection" },
          { value: "Videos Playlist", trait_type: "Playlist" },
          { value: "Video", trait_type: "Artwork Type" }
        ]
      }}},
    
      { metadata: { json: { 
        attributes: [
          { value: "Cool Original Artist", trait_type: "Artist" },
          { value: "Fridges Vol. 1", trait_type: "Collection" },
          { value: "Videos Playlist", trait_type: "Playlist" },
          { value: "Video", trait_type: "Artwork Type" }
        ]
      }}}
    ];
    
    const filters = [
      { "Artwork Type": ["Image", "Video"] },
      { Playlist: ["Videos Playlist"] },
      { Artist: ["Cool Original Artist"] }
    ];
    
    // reduce the arr by matching with each filter object
    const result = filters.reduce((remaining, curr) => {
    
      // assign filter attribute name and values
      const [name, values] = Object.entries(curr)[0];
      
      // do filtering
      return remaining.filter(({metadata: { json: {attributes}}}) =>
        attributes.find((a) => a.trait_type === name && values.includes(a.value))
      );
    }, arr);
    
    console.log(result);