3

I have an Array of Objects.

Each array of Objects contains an array items and each item in this array is an Object containing an array category:

var obj = [
  // first object
  {
    label: 'Label 1',
    // first items
    items: [
      {
        id: 1,
        itemName: 'Item Name 1',
        img: 'imgs/path-to1.jpeg',
        sizes: [],
        colors: [],
        // first category
        category: [
          'I',
          'E',
          'M'
        ],
        defaultChoices: {}
      },
      {
        id: 2,
        itemName: 'Item Name 2',
        img: 'imgs/path-to2.jpeg',
        sizes: [],
        colors: [],
        // second category
        category: [
          'I',
          'E'
        ],
        defaultChoices: {}
      },
      {
        id: 3,
        itemName: 'Item Name 3',
        img: 'imgs/path-to3.jpeg',
        sizes: [],
        colors: [],
        // third category
        category: [
          'I'
        ],
        defaultChoices: {}
      },
    ]
  },
  // second object
  {
    label: 'Label 2',
    // second items
    items: [
      {
        id: 7,
        itemName: 'Item Name 7',
        img: 'imgs/path-to7.jpeg',
        sizes: [],
        colors: [],
        // fourth category
        category: [
          'I',
          'M'
        ],
        defaultChoices: {}
      },
      ...

just to make the things clearer, a typical direct access to category would be performed this way: obj[0].items[0].category.

From the frontend of the app, the user, based on her choices, can send to the application one of the following arrays:

  • ['I'];
  • ['E'];
  • ['M'];
  • ['I','E'];
  • ['I','M'];
  • ['E','M'];
  • ['I','E','M'];
  • ...;

Then, the application should return the obj array filtered: if, for example, the user sent ['I'], the array should contain any objects where category contains 'I'. If the user sent ['E','M'], the array should contain any object where category contains ['E','M'] (no matter if category is ['E','M','I']), and so on.

I red a lot of docs about the JS filter function but I was not able to develop one function that, given the user array, can return a new array as result of filtering obj. I found dozen of docs with non-real-life examples like this one:

var hs = [
    {name: 'Batman', franchise: 'DC'},
    {name: 'Ironman', franchise: 'Marvel'}
];

var marvels =  heroes.filter(function(h) {
    return hs.franchise == 'Marvel';
});

Any help is appreciated.

[UPDATE] I add a more realistic data sample, sorry for not providing it before: https://drive.google.com/open?id=1sdRx6sQ-cnRXJ8YCe4QH2Sy5fa5mopYW

3
  • 1
    how should the result look like? do you want the objects with only nested wanted categories? mutated or a new one? Commented Sep 23, 2019 at 7:00
  • @NinaScholz: the resulting array obj must have the current structure, but if one category in one object doesn't match the user preferences, it should be removed from items (for example, if the user sends ['M'], the item with id=2 must be removed). I prefer a new array, anyway. Commented Sep 23, 2019 at 7:11
  • obj.map(obj => obj.items.filter(item => data.every(c => item.category.includes(c)))); Commented Sep 23, 2019 at 7:46

4 Answers 4

3

If you want to filter by category, which the number of object in the array is remained unchanged(even all items in that object are filter out and there is no item in the object), then it should be what you want:

var obj = [ // I removed some unnecessary params to make it clear, which shouldn't affect any
  {
    label: 'Label 1',
    items: [
      {
        id: 1,
        category: ['I', 'E', 'M'],
      },
      {
        id: 2,
        category: ['I', 'E'],
      },
      {
        id: 3,
        category: ['I'],
      },
    ]
  },
  {
    label: 'Label 2',
    items: [
      {
        id: 7,
        category: ['I', 'M'],
      },
    ]}
]


function filterByCategory(obj, categories) {
  return obj.map( o => ({
    ...o, // copy everything(i.e. label, items)
    items: o.items.filter(item => // modify items in the obj
       categories.some(c => item.category && item.category.includes(c)) // keep item if some categories are in item
    )
  }))
}
const filteredObj = filterByCategory(obj, ['I', 'E'])
console.log(filteredObj)

If you want to further filter out the object that there is no item, you can add .filter(o => o.items.length) at the end of filterByCategory.

Live Example:

var data = [ { label: 'Label 1', items: [{ id: 1, itemName: 'Item Name 1', img: 'imgs/path-to1.jpeg', sizes: [], colors: [], category: [ 'I', 'E', 'M' ], defaultChoices: {} }, { id: 2, itemName: 'Item Name 2', img: 'imgs/path-to2.jpeg', sizes: [], colors: [], category: [ 'I', 'E' ], defaultChoices: {} }, { id: 3, itemName: 'Item Name 3', img: 'imgs/path-to3.jpeg', sizes: [], colors: [], category: [ 'I' ], defaultChoices: {} }, ] }, { label: 'Label 2', items: [{ id: 7, itemName: 'Item Name 7', img: 'imgs/path-to7.jpeg', sizes: [], colors: [], category: [ 'I', 'M' ], defaultChoices: {} }] } ]; 

function filterByCategory(data, category) {
  return data.map(obj => {
    return { ...obj,
      "items": obj.items.filter(item =>
        category.some(value => item.category && item.category.includes(value))
      )
    };
  });
}

console.log(filterByCategory(data, ['E', 'M']));

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

11 Comments

Can you provide 2 usage examples?
This returns: "TypeError: Cannot read property 'includes' of undefined at categories.every.c" (I must say, I tried something similar a couple of hours ago and got the same error)
If you still get "TypeError: Cannot read property 'includes' of undefined ", then i think some of your items are undefined. In that case, do you want to remove all of them when filtering?
most probably OP passed an array to the method, thats why ended up with TypeError
Oh...should be category. Some items no category, which is undefined, not []?
|
2

This code iterates through all Objects and items and creates a new Object with only items that have the specified Categories. A more detailed description of the the Code is in the Comments:

// function where you enter the Objects and the Categories you want to filter
function getFilterObjectItems(objs, categories){
  // Filtered Objects
  return objs.map(function(obj) {
    // Creates a new Item
    var newObject = {label: obj.label, items:[]};
    //iterate through all items in an Object looking for items with matching Category
    obj.items.forEach(function(item){
      // if one category entry matches add item to new Object
      //  the "some" function returns "true" or "false" if one item matches the critieria
      if(item && item.category && item.category.some && item.category.some(cat => searchedCAT.indexOf(cat)>-1)){
        // the original item will be added to the new item ist
        newObject.items.push(item);
      }
    });
    return newObject;
  })
  // filters all New Objects that don't have items
  .filter(newObject => newObject.items.length>0);
}


// DATA from the Question
var obj = [
  // first object
  {
    label: 'Label 1',
    // first items
    items: [
      {
        id: 1,
        itemName: 'Item Name 1',
        img: 'imgs/path-to1.jpeg',
        sizes: [],
        colors: [],
        // first category
        category: [
          'I',
          'E',
          'M'
        ],
        defaultChoices: {}
      },
      {
        id: 2,
        itemName: 'Item Name 2',
        img: 'imgs/path-to2.jpeg',
        sizes: [],
        colors: [],
        // second category
        category: [
          'I',
          'E'
        ],
        defaultChoices: {}
      },
      {
        id: 3,
        itemName: 'Item Name 3',
        img: 'imgs/path-to3.jpeg',
        sizes: [],
        colors: [],
        // third category
        category: [
          'I'
        ],
        defaultChoices: {}
      },
    ]
  },]

// search Categories
var searchedCAT = ['I'];


// Calling the Function
console.info(getFilterObjectItems(obj, searchedCAT));

11 Comments

While your result very much resembles what I'm looking for, for some reason I get: TypeError: item.category is undefined when I run it in Firefox and Cannot read property 'some' of undefined if I test at repl.it Moreover, if you look for 'I', the function is not returning the item with id=3
@Guest thi i=§ was a minor error didn't check for -1. is found now. the some error I will check, right away on ff.
@Guest Are you sure the data is "save"? I added some check for item and item.category, maybe some items don't have category, with the provided data it work also on FireFox
Any items have category :-) I tried with your data and it works, I tried with mine and it doesn't. To avoid my boss killing me, I anonimize the real data and I post it as soon as possible.
@guest the problem is that item L3 Item Name 17 in Label 3 doesn't have a category
|
2
const UserChoice = ['E', 'X'];
const filtered = obj.filter(o => {
  const filteredItems = o.items.filter(item =>
    item.category.map(c => UserChoice.includes(c)).includes(true)
  );
  if (filteredItems.length > 0) {
    o.items = filteredItems;
    return o;
  }
});

"filtered" will have your filtered obj

Comments

1

This solution uses four Array methods to filter the obj array: .filter, .some, .every, and .includes.

// __Simplified version of the 'obj' array__
var obj = [
  {
    label: '1',
    items: [
      {id:1, category:['I','E','M']},
      {id:2, category:['I','E'] },
      {id:3, category:['I']}
    ]
  },
  {
    label: '2',
    items: [
      { id:7, category: ['I','M']}
    ]
  }
];


// __The filter function__
// (Takes an array of user input, and filters the global
//   `obj` array to return a new array)
function filterObj(inputArray){ 
  const filtered = obj.filter( object => // Keep the object if...
    object.items.some( item => // some item in the object passes this test:
      inputArray.every( value => // For every value in the input array...
        item.category.includes(value) // the item's 'category' array includes value
      )
    )
  );
  return filtered;
}

// __Testing function__
// (Calls `filterObj` on a test array and logs the results)
function test(input){
  console.log(`Testing this input: ${ input }`);
  console.log("Obects having `some` item where `every` value in the input array is `included`:");
  filterObj(input).forEach( matchingObject => { console.log(matchingObject.label); });
  console.log("");
}

// __Tests__
test(['I','M']);
test(['I','E']);

(Note: The output is according to the OP's original specification that "if, for example, the user sent ['I'], the array should contain any objects where category contains 'I' ")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.