使用 Ramda 合并由中间数组连接的两个数组

Merge two arrays connected by an intermediate array with Ramda

我最近开始使用 Ramda 处理来自 JSONAPI 的响应。我在处理复杂的关系和找出从多个对象获取我想要的数据的方法时遇到了一些麻烦。

一个用户被分配给一个user_role,而这个user_role被分配给一个角色。一个角色可以有多个user_role,但是一个user_role只能分配给一个角色。一个用户可以有多个 user_role,但为了简单起见,我只为每个用户分配了一个 user_role。

我的目标是获取 user_role 中引用的角色并将其放置在用户对象中的新 "included" 对象中。

例如:

获取这三组数据、用户、user_role 和角色:

const users = [
   {   
     id: 1, 
     attributes: {
       firstName: "Bob",
       lastName: "Lee"
     }, 
     relationships: {
       user_roles: {
         data: {
           id: 1, 
           type: "user_roles"
         }
       }
     },
     type: "users"
   },
   {   
     id: 2, 
     attributes: {
       firstName: "Kevin",
       lastName: "Smith"
     }, 
     relationships: {
       user_role: {
          data: {
            id: 2, 
            type: "user_roles"
          }
       }
     },
     type: "users"
   },
 ];

 const user_roles = [
   {
     id: 1,
     attributes: {
       createdAt: "7/3/2018",
       updatedAt: "7/3/2018"
     },
     relationships: {
       role: {
         data: {
           id: 3,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   },
   {
     id: 2,
     attributes: {
       createdAt: "7/1/2018",
       updatedAt: "7/1/2018"
     },
     relationships: {
       role: {
         data: {
           id: 4,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   } 
 ]

 const roles = [
   {  
     id: 3,
     attributes: {
       name: "manager",
       description: "manages stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 1,
             type: "user_roles"
           },
           { 
             id: 10,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
   {   
     id: 4,
     attributes: {
       name: "director",
       description: "directs stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 2,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
 ]

我需要的是一个如下所示的用户对象:

const newUser = [
   {   
     id: 1, 
     attributes: {
       firstName: "Bob",
       lastName: "Lee"
     }, 
     relationships: {
       user_roles: {
         data: {
         id: 1, 
           type: "user_roles"
         }
       }
     },
     type: "users",
     included: [
        {
          role: {
            name: "manager",
            description: "manages stuff"
          }
        }
     ]
   },
   {   
     id: 2, 
     attributes: {
       firstName: "Kevin",
       lastName: "Smith"
     }, 
     relationships: {
       user_role: {
         data: {
           id: 2, 
           type: "user_roles"
         }
       }
    },
    type: "users",
      included: [
        { 
          role: {
            name: "director",
            description: "directs stuff"
          }
        }
      ]
    }, 
  ];

我学会了如何将两个数组合并在一起,但是这个 "intermediate" 数组真的让我失望了,我什至不知道从哪里开始!

大合并:

为了解决这个问题,我创建了以下内容:

users.concat(user_roles).concat(roles).reduce((newArray, obj, _, arr) => newArray.find(obj2 => obj2.id === obj.id) ? newArray : newArray.concat(R.mergeAll(arr.filter(o => o.id === obj.id))), [])

我不确定这是否能满足您的需求,但这是它的工作方式:

  1. 将所有数组连接在一起;我们称它为 joinedArray
  2. 创建一个新数组; newArray
  3. 迭代joinedArray
    1. If 当前 id 存在于 newArray 中,不要向其推送任何内容。
    2. Else 使用 .filter 获取所有 id 并执行 R.mergeAll 合并所有这些。

您可以使用函数式编程来解决您的问题,使用数组的 map 方法作为休闲:

var newUser = users.map(function(user){
    var _user_role = user_roles.find(function(user_role){
        // here you get the user_role asociated to a user.
        return user_role.id === user.relationships.user_roles.data.id
    })
    var _role = roles.find(function(role){
        // here you get the role asociated to a user_role
        return role.id === _user_role.relationships.role.data.id
    })
    return {
        id: user.id,
        attributes: user.attributes,
        relationships: user.relationships,
        type: user.type,
        included: [{
            role: _role.attributes
        }]
    }
})

可能还会有一些优化,为了大家理解,我尽量简单了。

我的建议是将这些部分分解成单独的函数,然后将它们重新组合在一起。

注意:在下面的示例中,我已将嵌套在用户对象中的 user_roles 属性 更新为用户角色数组,如您的描述中所建议的那样。

首先,如果 id 经常查找这些项目,我建议创建这些列表的索引版本

const rolesIdx = R.indexBy(R.prop('id'), roles)
const userRolesIdx = R.indexBy(R.prop('id'), user_roles)

然后我们可以创建一个函数管道,当给定一个 user_role 对象时,它将创建所需的元素形状,这些元素最终将出现在 included 数组中。

const attributesForUserRole = R.pipe(
  R.path(['data', 'id']),
  R.flip(R.prop)(userRolesIdx),
  R.path(['relationships', 'role', 'data', 'id']),
  R.flip(R.prop)(rolesIdx),
  R.prop('attributes'),
  R.objOf('role')
)

然后我们可以创建一个函数,它将使用上面的 attributesForUserRole 函数将角色列表添加到 included 属性.

const addIncludedRoles = user =>
  R.assoc(
    'included',
    R.map(attributesForUserRole, user.relationships.user_roles),
    user
  )

这也可以以无点形式重写,尽管这可能会降低可读性(由您决定)。

const addIncludedRoles = R.chain(
  R.assoc('included'),
  R.o(R.map(attributesForUserRole), R.path(['relationships', 'user_roles']))
)

此时,只需使用 addIncludedRoles 函数映射 users 列表即可。

R.map(addIncludedRoles, users)

一起:

const users = [
   {   
     id: 1, 
     attributes: {
       firstName: "Bob",
       lastName: "Lee"
     }, 
     relationships: {
       user_roles: [{
         data: {
           id: 1, 
           type: "user_roles"
         }
       }]
     },
     type: "users"
   },
   {   
     id: 2, 
     attributes: {
       firstName: "Kevin",
       lastName: "Smith"
     }, 
     relationships: {
       user_roles: [{
          data: {
            id: 2, 
            type: "user_roles"
          }
       }]
     },
     type: "users"
   },
 ];

 const user_roles = [
   {
     id: 1,
     attributes: {
       createdAt: "7/3/2018",
       updatedAt: "7/3/2018"
     },
     relationships: {
       role: {
         data: {
           id: 3,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   },
   {
     id: 2,
     attributes: {
       createdAt: "7/1/2018",
       updatedAt: "7/1/2018"
     },
     relationships: {
       role: {
         data: {
           id: 4,
           type: "roles"
         }
       }
     },
     type: "user_roles"
   } 
 ]

 const roles = [
   {  
     id: 3,
     attributes: {
       name: "manager",
       description: "manages stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 1,
             type: "user_roles"
           },
           { 
             id: 10,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
   {   
     id: 4,
     attributes: {
       name: "director",
       description: "directs stuff"
     },
     relationships: {
       user_roles: {
         data: [
           { 
             id: 2,
             type: "user_roles"
           }
         ]
       } 
     },
     type: "roles"
   },
 ]

const rolesIdx = R.indexBy(R.prop('id'), roles)
const userRolesIdx = R.indexBy(R.prop('id'), user_roles)

const attributesForUserRole = R.pipe(
  R.path(['data', 'id']),
  R.flip(R.prop)(userRolesIdx),
  R.path(['relationships', 'role', 'data', 'id']),
  R.flip(R.prop)(rolesIdx),
  R.prop('attributes'),
  R.objOf('role')
)

const addIncludedRoles = user =>
  R.assoc(
    'included',
    R.map(attributesForUserRole, user.relationships.user_roles),
    user
  )

const result = R.map(addIncludedRoles, users)

console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>