-2

I have an object that looks like the following:

let responseData = [
{
   "name": "name",
   "other": "value",
   "anotherField": "blue",
   "appRoles": [
     {
         "code": "roleOne",
         "shouldDisplay": true
     }, 
     {
         "code": "roleTwo",
         "shouldDisplay": false
     }
   ]
}

I need to maintain the original structure all while keeping existing properties. I only want to remove/filter out any "appRoles" where "shouldDisplay" is false.

The following works, using a forEach and a filter operation to create a new object array, but is it possible to condense this even more?

let filteredApps;
responseData.forEach((team) => {
   let indyTeam = team;
   indyTeam.appRoles = team.appRoles.filter((role) => role.shouldDisplay === true);
   filteredApps.push(indyTeam);
});

When I use the map operation, I only get an array of the filtered appRoles - missing extra properties on each object such as "name":

let enabledAppRolesOnly =
      responseData.map((team) =>
          team.appRoles.filter((role) => role.shouldDisplay === true));
dotNetkow
  • 5,053
  • 4
  • 35
  • 50
  • would you like to get the outer object only if you have an element in `appRoles`? – Nina Scholz Aug 15 '17 at 18:31
  • It isn't clear what the criteria should be for including a `team` into `filteredApps`. Also `filteredApps` is `undefined`, and you're using a property called `displayByDefault`, which doesn't exist. – spanky Aug 15 '17 at 18:36
  • 1
    And `let indyTeam = team;` doesn't really do anything useful. What were you hoping for there? A deep copy of `team`? – spanky Aug 15 '17 at 18:37
  • @spanky yep! I want all team data, with just light filtering - remove roles where the displayByDefault field is false. – dotNetkow Aug 15 '17 at 18:48
  • So then the filtered `appRoles` should only be seen from `filteredApps` and not from the original `responseData`? In other words, `responseData` should be completely unaffected? – spanky Aug 15 '17 at 18:55

4 Answers4

2

array.map function calls a callback for each element of your array, and then push the return value of it to a new array.

from MDN doc:

map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).

So in your case, since you return team.appRoles.filter((role) => role.displayByDefault === true) which is your team array, you only get this.

What you could do would be this (in order to fully clone the object):

let responseData = [{
   "name": "name",
   "appRoles": [
     {
         "code": "roleOne",
         "shouldDisplay": true
     }, 
     {
         "code": "roleTwo",
         "shouldDisplay": false
     }
   ]
}]

let enabledAppRolesOnly = responseData.map(team => {
    const appRoles = team.appRoles.filter(role => role.shouldDisplay === true)
    return Object.assign({}, team, { appRoles })
});

console.log(enabledAppRolesOnly)
Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
  • Ah, OK. This works. I have other properties though. Is there any way to automatically return them all, without specifying them (as you've done with "name" above)? That'd be nice, especially if new fields are added over time. – dotNetkow Aug 15 '17 at 18:32
  • Yes there is, can I see the properties in order to give the most corresponding way to do it? – Ulysse BN Aug 15 '17 at 18:33
  • Updated Question with more fields... so any way to not specify each? Thus avoiding: return { name: team.name, other: team.other, anotherField: team.another }; and on and on..... – dotNetkow Aug 15 '17 at 18:35
  • 1
    I updated my answer, be careful though, [this wont work with nested properties](https://jsfiddle.net/7h7g2xxL/1/) – Ulysse BN Aug 15 '17 at 18:38
  • If you have concerned about nested property check [the best way to deep clone an object](https://stackoverflow.com/q/122102/6320039) – Ulysse BN Aug 15 '17 at 18:43
  • Nice, clean answer. Close but.... the appRoles in each are being returned as empty arrays. Is that what you mean by nested properties? – dotNetkow Aug 15 '17 at 18:46
  • I used `shouldDisplay` rule, but there may be an error in your question since you're creating object with this property and then filtering by `displayByDefault`. I guessed the first one was the right one, let me know if i should edit – Ulysse BN Aug 15 '17 at 18:48
  • @dotNetkow my answer is even more concise than the above answer. You could take a look. – Yasser Hussain Aug 15 '17 at 19:14
1

This will achieve your objective non-destructively. It will build a new array for you.

let responseData = [{
    name: "name",
    appRoles: [{
        code: "roleOne",
        shouldDisplay: true
    }, {
        code: "roleTwo",
        shouldDisplay: false
    }]
}];

let output = responseData.map(o => Object.assign({}, o, {
    appRoles: o.appRoles.filter(r => r.shouldDisplay)
}));

console.log(responseData);
console.log(output);

Code explanation -

map

The map function iterates over the whole array and modifying the each item as specified this should be self evident.

Object.assign

This could be the tricky part -

o=>Object.assign({}, o, {appRoles: o.appRoles.filter(r=>r.shouldDisplay)})

From the docs Object.assign is used to copy values from the object.

  • The first argument {} causes a new object to be created.
  • The second argument o causes all props from the object o to be copied in the newly created object.
  • Now, note that we need to modify the appRoles property and keep only those roles which have shouldDisplay as true. That's exactly what the third argument does. It modifies the appRoles property and gives it the new value.

filter

Now the code -

o.appRoles.filter(r=>r.shouldDisplay)

should not be too difficult. Here we keep only those roles which meet our criterion (namely shouldDisplay should be true)

If you look at the filter function, it expects the callback value to return a boolean value on whose basis it determines whether value has to be kept or not.

So the following code is not even required,

o.appRoles.filter(r=>r.shouldDisplay===true)

This is enough,

o.appRoles.filter(r=>r.shouldDisplay)
Yasser Hussain
  • 854
  • 7
  • 21
  • By the time you updated your answer to include `Object.assign` I had already written my answer including `Object.assign`. By that logic I can say you duplicated my answer. – Yasser Hussain Aug 15 '17 at 19:20
  • But you answered first. Your answer is accepted. It's fine. – Yasser Hussain Aug 15 '17 at 19:21
  • Ulysse had the more complete answer (in the beginning) and the one I happened to see and work through first. Upvoted you though, Yasser! Much appreciated. – dotNetkow Aug 15 '17 at 20:24
0

You could build a new array with only part who are valid chinldren elements.

let responseData = [{ name: "name", appRoles: [{ code: "roleOne", shouldDisplay: true }, { code: "roleTwo", shouldDisplay: false }] }],
    result = responseData.reduce((r, a) => {
        var t = a.appRoles.filter(o => o.shouldDisplay);
        if (t.length) {
            r.push(Object.assign({}, a, { appRoles: t }));
        }
        return r;
    }, []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

There's some missing information in the question. I'll assume the following:

  • filteredApps should only contain items where there's at least one appRole for display
  • it is OK if responseData and filteredApps contains references to the same team objects
  • there are no other references to team objects that need to keep the original data unaffected

As such, you can reduce your code down to this:

let filteredApps = responseData.filter(team =>
   (team.appRoles = team.appRoles.filter(role => role.shouldDisplay)).length;
);

The result will be that each team will have only the .shouldDisplay members in its appRoles, and filteredApps will only have teams with at least one appRole with shouldDisplay being true.

spanky
  • 2,768
  • 8
  • 9