48

I am trying to filter an array, based on some nested object. I prepared some Fiddle

Input array looks like this:

let arrayOfElements = 
    [
        {
           "name": "a",
           "subElements": 
           [
             {"surname": 1},
             {"surname": 2}
           ]
        },
        {
           "name": "b",
           "subElements": 
           [
             {"surname": 3},
             {"surname": 1}
           ]
        },
        {
           "name": "c",
           "subElements": 
           [
             {"surname": 2},
             {"surname": 5}
           ]
        }
    ];

I want the output for this case, to look like this:

let filteredArray = 
    [
        {
          "name": "a",
          "subElements": 
          [
            {"surname": 1}
          ]
        },
        {
          "name": "b",
          "subElements": 
          [
            {"surname": 1}
          ]
        }
];

I am using this formula to do that:

let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === 1));

Output is almost good, but it returns objects with all objects with surnames (better check that fiddle :D), instead of cutting them away. How can i improve the filtering ?

miradulo
  • 28,857
  • 6
  • 80
  • 93
bartosz.baczek
  • 1,567
  • 1
  • 17
  • 32
  • Are you trying to group based on surname? I mean same thing should group `a` and `c` since they have surname `2`. Right? – Rajesh Jul 14 '16 at 13:34

9 Answers9

72

This way you can go as deep as you want in an array and filter elements at any level,

arrayOfElements.map((element) => {
  return {...element, subElements: element.subElements.filter((subElement) => subElement.surname === 1)}
})

Spread operator will expand element and then filtered subElements will override the subElements in element.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Nitesh Ranjan
  • 1,221
  • 1
  • 13
  • 13
  • this should be the answer because the accepted answer is not producing the expected output – Lokesh Sanapalli Feb 20 '20 at 07:18
  • Yes, this answer is better. When the accepted answer was written, the Object spread operator did not exist. – Andrew Eisenberg Feb 20 '20 at 22:27
  • This is modifying original array – Aadam May 31 '20 at 14:32
  • 1
    @Aadam can you share your array ? Array.map doesn't modify the original array but returns a new array with modified values. – Nitesh Ranjan Jun 06 '20 at 16:30
  • 3
    This solution has the problem that is will return the same number of elements as the original. They will have the subElements array empty, so a correction would be to add a filter to the result map by checking the length of subElements. – user3658510 Aug 16 '21 at 20:18
  • @Nitesh Ranjan Its filtering only 2 levels.. After that its not filtering. Any solutions for that? – VBC Sep 25 '21 at 17:25
  • 1
    This is not removing the third object completely – Raphael Dec 16 '21 at 18:10
  • Filter the result array to delete the third empty element let filteredArray = arrayOfElements.map((element) => { return {...element, subElements: element.subElements.filter((subElement) => subElement.surname === 1)} }).filter(elem => elem.subElements.length > 0); – Babitha Aug 29 '22 at 22:51
  • Here, how to filter for name = a and also surname = 1? – Sohan Dec 26 '22 at 21:25
29

After you call filter, you need to pipe the results to map, like this:

let filteredArray = arrayOfElements
  .filter((element) => 
    element.subElements.some((subElement) => subElement.surname === 1))
  .map(element => {
    let newElt = Object.assign({}, element); // copies element
    return newElt.subElements.filter(subElement => subElement.surname === '1');
  });

I am assuming here that you don't want to manipulate the original array. So, I am using Object.assign.

Andrew Eisenberg
  • 28,387
  • 9
  • 92
  • 148
  • 3
    isn't this returning just the array of subElements instead of the full object with the filtered subarray? – Wanny Miarelli Jan 21 '18 at 12:17
  • 1
    @WannyMiarelli is correct. It will return the filtered subarrays and not the full object with the filtered surnames. Also the code above has errors (string check instead of integer and typo surName). – girls_can_code_too Nov 16 '19 at 15:06
  • 1
    this solution is not producing expected output : https://jsbin.com/dunuqeyeje/edit?js,console – Lokesh Sanapalli Feb 20 '20 at 07:18
  • @LokeshSanapalli, in your final call to `filter` you need to change `surName` to `surname`. Then it works. That was my mistake above. Corrected now. – Andrew Eisenberg Feb 20 '20 at 22:25
  • Hi @AndrewEisenberg! Thanks for your input. Now after 4 years I've decided to change the accepted answer to one that is using spread operator since it seems to be better suited for 2020 :) Thanks, and no hard feelings, I hope :) – bartosz.baczek Sep 24 '20 at 06:47
  • This answer still has problems and should be edited. In the last filter change the '1' to 1 – user3658510 Aug 16 '21 at 20:09
23
let filteredArray = arrayOfElements
  .filter((element) => 
    element.subElements.some((subElement) => subElement.surname == 1))
  .map(element => {
    return Object.assign({}, element, {subElements : element.subElements.filter(subElement => subElement.surname == 1)});

  }); 
Mohak Londhe
  • 372
  • 2
  • 9
8

Just improved the answers above

let elements = 
    [
        {
           "name": "a",
           "subElements": 
           [
             {"surname": 1},
             {"surname": 2}
           ]
        },
        {
           "name": "b",
           "subElements": 
           [
             {"surname": 3},
             {"surname": 1}
           ]
        },
        {
           "name": "c",
           "subElements": 
           [
             {"surname": 2},
             {"surname": 5}
           ]
        }
    ];
var value = 1;

var filteredArray = elements
.filter(element => element.subElements
  .some(subElement => subElement.surname === value)
)
.map(element => {
  let n = Object.assign({}, element, {'subElements': element.subElements.filter(
    subElement => subElement.surname === value
  )})
  return n;
})

console.log(filteredArray)
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
7

Try this solution:

data_filter = arrayOfElements.filter(function (element) {
    return element.subElements.some( function (subElement) {
        return subElement.surname === surname
    });
});
NatNgs
  • 874
  • 14
  • 25
Praveenkarthi
  • 89
  • 1
  • 7
1

You can make it generic as well:

Logic

  • Find all distinct surnames and loop over them
  • Filter every object to check if surnames exists. If yes, copy object using Object.assign and set subElements value to filtered list.
  • Create a temp array to hold all similar objects and push copied object to it.
  • Push this array to final array on every iteration of distinct surname.

Sample

let arrayOfElements=[{name:"a",subElements:[{surname:1},{surname:2}]},{name:"b",subElements:[{surname:3},{surname:1}]},{name:"c",subElements:[{surname:2},{surname:5}]}];
 let distinct_surnames = [];
 arrayOfElements.forEach(function(el) {
   el.subElements.forEach(function(s) {
     if (distinct_surnames.indexOf(s.surname) < 0) distinct_surnames.push(s.surname)
   });
 })

 let result = [];
 distinct_surnames.forEach(function(sn) {
   let inter = [];
   arrayOfElements.forEach(function(el) {
     let f = el.subElements.filter(function(sub) {
       return sub.surname === sn;
     });
     if (f.length > 0) {
       let _tmp = Object.assign({}, el);
       _tmp.subElements = f;
       inter.push(_tmp);
     }
   });
   result.push(inter);
 })
 console.log(result)

Note: Arrow functions are used to keep the reference of this. If you are not using this inside function, you can use normal functions as well.

Community
  • 1
  • 1
Rajesh
  • 24,354
  • 5
  • 48
  • 79
0

function display_message() {
  let arrayOfElements = [{
    "name": "a",
    "subElements": [{
      "surname": 1
    }, {
      "surname": 2
    }]
  }, {
    "name": "b",
    "subElements": [{
      "surname": 3
    }, {
      "surname": 1
    }]
  }, {
    "name": "c",
    "subElements": [{
      "surname": 2
    }, {
      "surname": 5
    }]
  }];
  // console.log(arrayOfElements);
var surname = 1;
  let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === surname));

  for(var data in filteredArray){
    filteredArray[data].subElements = {"surname": surname};
    }
  console.log(filteredArray);

}
<input type="button" onclick="display_message();" value="click"/>
Himanshu Tyagi
  • 5,201
  • 1
  • 23
  • 43
0
let filteredArray = arrayOfElements
    .filter((element) => 
        element.subElements.some((subElement) => subElement.surname === 1))
    .map(element => {
        let newElt = Object.assign({}, element); // copies element
        newElt.subElements = newElt.subElements.filter(subElement => subElement.surName === '1'); 
        return newElt;
    });

is more correctly

Ilya Alexeev
  • 232
  • 2
  • 6
0

let dataSource = [
  {
    groupName: 'FORD',
    optionItem: [
      {
        label: 'Fiesta',
        data: 'Fiesta',
      },
      {
        label: 'Fusion',
        data: 'Fusion',
      },
      {
        label: 'Mustang',
        data: 'Mustang',
      },
    ],
  },
  {
    groupName: 'HONDA',
    optionItem: [
      {
        label: 'Accord',
        data: 'Accord',
      },
      {
        label: 'Civic',
        data: 'Civic',
      },
      {
        label: 'CR-V',
        data: 'CR-V',
      },
    ],
  },
  {
    groupName: 'HYUNDAI',
    optionItem: [
      {
        label: 'Accent',
        data: 'Accent',
      },
      {
        label: 'Sonata',
        data: 'Sonata',
      },
      {
        label: 'Tucson',
        data: 'Tucson',
      },
    ],
  },
];

let filterValue = dataSource.filter(a => {
  a.optionItem = a.optionItem.filter(
    option => option.data.toLowerCase().indexOf('Fiesta'.toLowerCase()) !== -1
  );
  return a.optionItem.length;
});

console.log('filter value ', filterValue);


/**
OUTPUT
=========================================
filter value =>  [{
  "groupName": "FORD",
  "optionItem": [
    {
      "label": "Fiesta",
      "data": "Fiesta"
    }
  ]
}] 
**/
Varadha Raj
  • 63
  • 1
  • 1
  • 10