减少包含几乎相同对象的数组

时间:2019-01-08 20:09:28

标签: javascript arrays lodash

我正在尝试找到一种有效且尽可能短的方法,将数组中的几乎相同的对象折叠成包含自己的带有不相同数据的数组的对象。当我尝试解释时,听起来很复杂,请允许我告诉您我的意思:

我有一个对象数组,如下所示:

    [{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'User',                                // 0,1
        rAuthority: 'ROLE_USER',                      // 0,1
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'User',                                // 0,1
        rAuthority: 'ROLE_USER',                      // 0,1
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'Admin',                               // 2,3
        rAuthority: 'ROLE_ADMIN',                     // 2,3
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
        atPlatform: 'web',
    },{ 
        id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
        first_name: 'SomeName',
        email: 'some@email.com',
        rName: 'Admin',                               // 2,3
        rAuthority: 'ROLE_ADMIN',                     // 2,3
        pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
        pMobile: '012 345 6789',
        atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
        atPlatform: 'web',
    }]

// I point out which of the properties are not identical by adding 
// quotes showing which indices of the array contains unique values of 
// said property. If the there's not a quote to the right of the 
// property it's identical across all indices.

我想将此数组转换为一个数组,在该数组中,具有重复ID的对象将折叠为一个对象,其中对象数组包含不同的数据。看起来像这样:

[{
    id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
    first_name: 'SomeName',
    email: 'some@email.com',
    roles: [{
            name: 'User',
            authority: 'ROLE_USER'
    },{
            name: 'Admin',
            authority: 'ROLE_ADMIN'
    }],
    profiles: [{
            id: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
            mobile: '012 345 6789',
    }],
    tokens: [{
            id: '90db0c5d-3030-44aa-9dc0-40242af0d5c5',
            platform: 'web',
    },{
            id: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c',
            platform: 'web',
    }]
}]

如您所见,先前数组中所有带有前缀r的属性现在都在roles属性中的数组中拥有自己的对象。 p前缀属性在profiles中,而at前缀属性在tokens中。如果一个对象与另一个对象具有相同的id,则该对象被视为“相同”。

这里我写了一些代码,似乎可以成功地将第一个数组转换为第二个数组:

...then(data => {

    let users = [];

    // gets the correct formatting
    data.forEach((d, i) => {
        let found = false;
        users.forEach((u, j) => {
            if(d.id === u.id) {
                u.roles.push({ name:d.rName, authority:d.rAuthority });
                u.tokens.push({ id:d.atId, platform:d.atPlatform });
                u.profiles.push({ id:d.pId, mobile:d.pMobile });
                found = true;
            }
        });
        if(!found) {
            users.push({
                id:d.id,
                first_name:d.first_name,
                email:d.email,
                roles: [{ name:d.rName, authority:d.rAuthority }],
                profiles: [{ id:d.pId, mobile:d.pMobile }],
                tokens: [{ id:d.atId, platform:d.atPlatform }]
            });
        }
    });

    // remove duplicates from sub-arrays
    users.forEach((user, i) => {
        user.roles = _.uniqBy(user.roles, 'name');
        user.profiles = _.uniqBy(user.profiles, 'id');
        user.tokens = _.uniqBy(user.tokens, 'id');
    });
});

此代码有两个问题。首先是超长的(出于这个问题,我实际上从第一个数组中删除了许多属性-实际上,每个对象的属性数量是您在此处看到的数量的两倍以上,从而使代码更长),并且其次,我强烈怀疑它可能效率很低。

问题:

有人可以帮我重写我用来将数组格式化为更短,更有效的代码的代码。我确实安装了lodash,所以我更喜欢使用它的答案,但我也很乐意也接受vanilla.js答案。

其他说明:

这个问题是我发布的another question的后续内容。查看该问题将模糊地向您显示我要在此问题中转换的数据来自何处。简短的版本是它来自数据库,这是我使用Knex.js进行延迟加载的想法。这个想法是,每个用户可以具有多个角色,配置文件和AuthToken。

4 个答案:

答案 0 :(得分:1)

您可以使用reducer并构建新的对象数组:

const input = [{
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'User', // 0,1
  rAuthority: 'ROLE_USER', // 0,1
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'User', // 0,1
  rAuthority: 'ROLE_USER', // 0,1
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'Admin', // 2,3
  rAuthority: 'ROLE_ADMIN', // 2,3
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', // 0,2
  atPlatform: 'web',
}, {
  id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd',
  first_name: 'SomeName',
  email: 'some@email.com',
  rName: 'Admin', // 2,3
  rAuthority: 'ROLE_ADMIN', // 2,3
  pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e',
  pMobile: '012 345 6789',
  atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', // 1,3
  atPlatform: 'web',
}];

console.log(input.reduce((acc, val, ind) => {
  if (!acc.find(el => el.id === val.id)) {
    acc.push({
      id: val.id,
      first_name: val.first_name,
      email: val.email,
      roles: [],
      profiles: [],
      tokens: []
    });
  }
  else {
    const el = acc.find(el => el.id === val.id);
    if (!el.roles.find(a => a.authority === val.rAuthority)) {
      el.roles.push({
        authority: val.rAuthority,
        name: val.rName
      });
    }
    if (!el.profiles.find(a => a.id === val.pId)) {
      el.profiles.push({
        id: val.pId,
        mobile: val.pMobile
      });
    }
    if (!el.tokens.find(a => a.id === val.atId)) {
      el.tokens.push({
        id: val.atId,
        platform: val.atPlatform
      });
    }
  }
  return acc;
}, []));

答案 1 :(得分:1)

一种与quirimmo答案略有不同的方法,即使用对象作为中间值将每个项目/子项目映射到其对应的标识符,然后使用Object.values将其还原:

const input = [
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web'},
  {id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web'}
];

const result = Object.values(input.reduce((result, item) => {
  if (!(item.id in result)) {
    result[item.id] = {id: item.id, first_name: item.first_name, email: item.email, roles: {}, profiles: {}, tokens: {}};
  }
  result[item.id].roles[item.rName] = {name: item.rName, authority: item.rAuthority};
  result[item.id].profiles[item.pId] = {id: item.pId, mobile: item.pMobile};
  result[item.id].tokens[item.atId] = {id: item.atId, platform: item.atPlatform};
  return result;
}, {})).map(item => {
  ['roles', 'profiles', 'tokens'].forEach(prop => item[prop] = Object.values(item[prop]));
  return item;
});

console.log(result);

基本上:

  • Array.reduce将初始数组转换为由项目的标识符索引的对象,并包含也由其相应的标识符索引的角色/个人资料/令牌对象,
  • Object.values将结果对象转换回数组,
  • Array.map对每个项目的角色/配置文件/令牌调用Object.values,以将它们转换为所需的数组。

答案 2 :(得分:1)

使用lodash,您可以创建部分应用的函数,该函数使用_.pickBy()和带有该属性开头的字符串的正则表达式从对象获取属性。然后,您可以使用相关字符串(at的{​​{1}})来创建属性创建者。

现在tokens个项目_.groupBy(),并映射每个组以使用属性创建器创建对象。

id
const data = [{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"User","rAuthority":"ROLE_USER","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"90db0c5d-3030-44aa-9dc0-40242af0d5c5","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"User","rAuthority":"ROLE_USER","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"e7d53cab-a9b9-40ae-9271-11d79c2f269c","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"Admin","rAuthority":"ROLE_ADMIN","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"90db0c5d-3030-44aa-9dc0-40242af0d5c5","atPlatform":"web"},{"id":"6b6574cf-d77a-4ed8-852f-cb60a0d377cd","first_name":"SomeName","email":"some@email.com","rName":"Admin","rAuthority":"ROLE_ADMIN","pId":"e7da65a9-ea2d-4c77-82f6-e1addc78fb6e","pMobile":"012 345 6789","atId":"e7d53cab-a9b9-40ae-9271-11d79c2f269c","atPlatform":"web"}];
    
const createAttribute = startStr => {
  const pattern = new RegExp(`^${startStr}[A-Z]`);
  
  return arr => _.uniqWith(_.map(arr, _.flow([
    obj => _.pickBy(obj, (v, k) => pattern.test(k)),
    obj => _.mapKeys(obj, (v, k) => 
      k.replace(/(^[a-z]+)([A-Z].+$)/, '$2') // key name without the prefix
      .toLowerCase()
    )
  ])), _.isEqual);
};

const createRoles = createAttribute('r');
const createProfiles = createAttribute('p');
const createTokens = createAttribute('at');
    
const fn = _.flow([
  arr => _.groupBy(arr, 'id'),
  groups => _.map(groups, group => {
    const { id, first_name, email } = _.first(group);
    
    return {
      id,
      first_name,
      email,
      roles: createRoles(group),
      profiles: createProfiles(group),
      tokens: createTokens(group)
    };
  })
]);

const result = fn(data);

console.log(result);

答案 3 :(得分:1)

您可以使用其键和别名为所需的分组取一些数组。

var data = [{ id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'User', rAuthority: 'ROLE_USER', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: '90db0c5d-3030-44aa-9dc0-40242af0d5c5', atPlatform: 'web' }, { id: '6b6574cf-d77a-4ed8-852f-cb60a0d377cd', first_name: 'SomeName', email: 'some@email.com', rName: 'Admin', rAuthority: 'ROLE_ADMIN', pId: 'e7da65a9-ea2d-4c77-82f6-e1addc78fb6e', pMobile: '012 345 6789', atId: 'e7d53cab-a9b9-40ae-9271-11d79c2f269c', atPlatform: 'web' }],
    head = [['id'], ['first_name'], ['email']],
    sub = [
        ['roles', [['name', 'rName'], ['authority', 'rAuthority']]],
        ['profiles', [['id', 'pId'], ['mobile', 'pMobile']]],
        ['tokens', [['id', 'atId'], ['platform', 'atPlatform']]]
    ],
    result = data.reduce((r, o) => {
        const mapData = ([key, alias = key]) => ({ [key]: o[alias] });
        var temp = r.find(q => q[head[0]] === o[head[0]]);
        if (!temp) {
            r.push(temp = Object.assign(...head.map(mapData)));
        }
        sub.forEach(([s, keys]) => {
            temp[s] = temp[s] || [];
            var inner = temp[s].find(q => q[keys[0][0]] === o[keys[0][1]]);
            if (!inner) {
                temp[s].push(Object.assign(...keys.map(mapData)));
            }
        });
        return r;
    }, []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }