How to remove repeated elements from an object array by preserving the object that has a different id property than null

Asked

Viewed 201 times

1

I need to remove the elements that have the same value in the property screen_id keeping whatever has the property id other than null.

My sample object:

let userPermissions  = [
    {
        id: 1,
        description: Cadastro de usuários,
        screen_id: 1,
        allow_read: true,
        allow_create: true,
        allow_update: false,
        allow_delete: true
    },
    {
        id: null,
        description: "Cadastro de usuários",
        screen_id: 1,
        allow_read: false,
        allow_create: false,
        allow_update: false,
        allow_delete: false
    },
    {
        id: null,
        description: "Tela teste 3",
        screen_id: 5,
        allow_read: false,
        allow_create: false,
        allow_update: false,
        allow_delete: false
    },
]

I need the following result:

[
    {
        "id": 1,
        "description": "Cadastro de usuários",
        "screen_id": 1,
        "allow_read": true,
        "allow_create": true,
        "allow_update": false,
        "allow_delete": true
    },
    {
        "id": null,
        "description": "Tela teste 3",
        "screen_id": 5,
        "allow_read": false,
        "allow_create": false,
        "allow_update": false,
        "allow_delete": false
    },
]

What I tried to:

userPermissions = userPermissions.reduce((arr, item) => {
  const removed = arr.filter(i => i['screen_id'] === item['screen_id'] && item['id'] !== null)
  return [...removed, item]
}, [])

But not returning the result as expected.

2 answers

3

If you want a slightly more performative solution, you can use a loop for conventional:

const userPermissions = [
  { id: null, description: 'Tela teste 4', screen_id: 6 },
  { id: null, description: 'Cadastro de usuários', screen_id: 1 },
  { id: null, description: 'Tela teste 3', screen_id: 5 },
  { id: 8, description: 'Tela teste 4', screen_id: 6 },
  { id: 1, description: 'Cadastro de usuários', screen_id: 1 }
];

function removeDuplicates(list) {
  const listed = {};

  for (const item of list) {
    const { id, screen_id: screenId } = item;

    // Caso um item já esteja sido listado e seu ID não seja nulo, não será
    // adicionado à lista.
    if (screenId in listed && listed[screenId].id !== null) {
      continue;
    }

    // Caso ainda não esteja listado, ou já esteja listado, porém com um ID
    // nulo, será adicionado à lista.
    if (
      !(screenId in listed) ||
      (listed[screenId].id === null && id !== null) // Irá garantir que itens com ID não nulo sejam reinseridos.
    ) {
      listed[screenId] = item;
    }
  }

  return Object.values(listed);
}

console.log(removeDuplicates(userPermissions));

It’s a little verbose, but since it only has an iteration loop, I think it’s the best alternative if you need performance... You can even do it with reduce, but I think I’d be half disgusting...

  • 1

    That way I always have to make sure that my element that has id !== null appears before the null id element, right?

  • 2

    I tried to do with reduce, but in fact it was getting so "disgusting" that I gave up :-)

  • Yes, @veroneseComS, elements that have a null ID will be prioritized. Code part responsible so it is: || (listed[screenId] === null && id !== null), in the evaluation expression of the second if.

  • I did some tests with your code, if my element repeated with id null appears first that the element repeated with id !== null, it is inserted. Here is a reproduction: https://playcode.io/523408

  • Really, @veroneseComS, my mistake. Sorry. :v I edited the answer.

  • @Luizfelipe thank you!

Show 1 more comment

3


Although "cool", it is not always easier to think in a "functional" way, so before trying to do with reduce, why not use a loop simple?

let userPermissions  = [
    {
        id: 1,
        description: "Cadastro de usuários",
        screen_id: 1,
        allow_read: true,
        allow_create: true,
        allow_update: false,
        allow_delete: true
    },
    {
        id: null,
        description: "Cadastro de usuários",
        screen_id: 1,
        allow_read: false,
        allow_create: false,
        allow_update: false,
        allow_delete: false
    },
    {
        id: null,
        description: "Tela teste 3",
        screen_id: 5,
        allow_read: false,
        allow_create: false,
        allow_update: false,
        allow_delete: false
    }
];

let result = [];
let screenIdsJaVistos = {};
for (const u of userPermissions) {
    // se o screen_id ainda não foi verificado
    if (!screenIdsJaVistos[u.screen_id]) {
        // buscar todos que tem o mesmo screen_id
        let mesmoScreenId = userPermissions.filter(user => user.screen_id == u.screen_id);
        if (mesmoScreenId.length > 1) { // se tem mais de um, pega o que tem id não nulo
            result.push(mesmoScreenId.find(user => user.id !== null));
        } else { // senão, pega o próprio elemento
            result.push(u);
        }
        // esse screen_id já foi verificado, posso pular nas próximas iterações
        screenIdsJaVistos[u.screen_id] = true;
    }
}
console.log(result);

Of course, if you have several elements with the same screen_id, He’ll get the first one that doesn’t have id null. But if you need another tiebreaker, just add above, within the if (mesmoScreenId.length > 1).

In my opinion it’s a much clearer code than using reduce but of course there are those who disagree, so it is "opinion" - in addition, it is easier to add new conditions if necessary.

The code may even get bigger than it used reduce, but the size of the code should not be an end in itself. A clearer and easier to understand and maintain code is much better than a short but complicated code (unless you and your team feel comfortable with reduce, clear-cut).

  • It works well in this example but if the first repeated screen_id is id null, it puts this element instead of the element that has id !== null. Example of reproduction: https://playcode.io/523408

  • @veroneseComS It was a typo, no filter should be user.screen_id == u.screen_id but I was using u.id. I already corrected

  • Thanks for the solution, now it meets my need and really, it’s easier to keep a code for() than a code with reduce (I’m not yet aware of the 1001 utilities that a Reducer can do)

  • 1

    @veroneseComS In my opinion, there is a certain abuse of the reduce, many people use without thinking, even when a simple loop is the best solution (not to mention that it is usually slower too, although for arrays).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.