使用 Ramda.js 从对象中提取项目

Use Ramda.js to pull off items from object

这个问题是关于如何使用 RamdaJS 执行任务的。

首先,假设我有一个具有这种结构的对象:

let myObj = {
  allItems: [
    {
      name: 'firstthing',
      args: [
        {
          name: 'arg0'
        },
        {
          name: 'arg1'
        }
      ],
      type: {
        name: 'type_name_1'
      }
    },
    {
      name: 'otherthing',
      args: [
        {
          name: 'arg0'
        }
      ]
    }
  ]
}

我正在尝试创建一个如下所示的对象:

{
    arg0: 'arg0', // myObj.allItems[0].args[0].name
    typeName: 'type_name_1' // myObj.allItems[0].type.name
}

(我知道名字很蠢,arg0,typeName。这不重要)

所以如果我们不使用 Ramda,这就是我强制执行的方式:

// The thing I'm searching for in the array (allItems)
let myName = 'firstthing';
// Here's how I'd find it in the array
let myMatch = myObj.allItems.find(item => item.name === myName);
// Here is the desired result, by manually using dot 
// notation to access properties on the object (non-functional)
let myResult = {
  arg0: myMatch.args[0].name,
  typeName: myMatch.type.name
};
// Yields: {"arg0":"arg0","typeName":"type_name_1"}
console.log(myResult)

最后,为了更好的衡量,这就是我到目前为止所取得的进展。请注意,我真的很想在一个 compose/pipe 中完成此操作。 (一个物体进去,一个带有所需数据的物体出来)

const ramdaResult = R.compose(
  R.path(['type', 'name']),
  R.find(
    R.propEq('name', myName)
  )
)(R.prop('allItems', myObj))

谢谢

applySpec and path 的组合应该有效:

const transform = applySpec ({
  arg0: path (['allItems', 0, 'args', 0, 'name']),
  typeName: path (['allItems', 0, 'type', 'name'])
})

const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}

console .log (
  transform (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {applySpec, path} = R                                  </script>

但根据您的喜好,辅助函数可能有助于简化一些 API:

const splitPath = useWith (path, [split('.'), identity] )
// or splitPath = curry ( (str, obj) => path (split ('.') (str), obj))

const transform = applySpec({
  arg0: splitPath('allItems.0.args.0.name'),
  typeName: splitPath('allItems.0.type.name'),
})

const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}

console .log (
  transform (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {applySpec, path, useWith, split, identity} = R                                        </script>

splitPath 不适合 Ramda,但它是我经常包含的一个有用的函数,尤其是当路径来自我无法控制的来源时。

更新

是的,我确实错过了那个要求。为我服务只看输入和请求的输出。总是有多种不兼容的算法为特定输入提供相同的结果。所以这是我的 mea culpa,试图将其分解为几个可重用的函数。

镜头可能是您最好的选择。 Ramda 有一个泛型 lens function, and specific ones for an object property (lensProp), for an array index(lensIndex), and for a deeper path(lensPath),但它不包括通过 id 在数组中查找匹配值的方法。不过,我们自己制作并不难。

通过将两个函数传递给 lens 来制作镜头:一个 getter 获取对象和 returns 相应的值,以及一个 setter 获取新值和对象以及 returns 对象的更新版本。

关于镜头的一个重要事实是它们是组成的,尽管由于技术原因,您提供它们的顺序感觉与您的预期相反。

这里我们写 lensMatch 查找或设置数组中给定路径的值与提供的值匹配的值。我们写 applyLensSpec,它的作用类似于 applySpec,但用透镜代替了原版函数。

使用任何镜头,我们都有 view, set, and over 函数,分别获取、设置和更新值。这里我们只需要view,所以理论上我们可以做一个更简单的lensMatch版本,但这可能是一个有用的可重用函数,所以我保持完整。

const lensMatch = (path) => (key) => 
  lens
    ( find ( pathEq (path, key) )
    , ( val
      , arr
      , idx = findIndex (pathEq (path, key), arr)
      ) =>
        update (idx > -1 ? idx : length (arr), val, arr)
    )

const applyLensSpec = (spec) => (obj) =>
  map (lens => view (lens, obj), spec)

const lensName = (name) => lensMatch (['name']) (name)

const transform = (
  name, 
  nameLens = compose(lensProp('allItems'), lensName(name))
) => applyLensSpec({
  arg0: compose (nameLens, lensPath (['args', 0, 'name']) ),
  typeName: compose (nameLens, lensPath (['type', 'name']) )
})

const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}


console .log (
  transform ('firstthing') (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lens, find, pathEq, findIndex, update, length, map, view, compose, lensProp, lensPath} = R                                        </script>

虽然这可能感觉比其他一些解决方案需要更多工作,但主要功能 transform 非常简单,并且很明显如何通过其他行为扩展它。 lensMatchapplyLensSpec 真的很有用。