Ramda js:具有嵌套对象数组的深度嵌套对象的镜头

Posted

技术标签:

【中文标题】Ramda js:具有嵌套对象数组的深度嵌套对象的镜头【英文标题】:Ramda js: lens for deeply nested objects with nested arrays of objects 【发布时间】:2016-06-02 23:26:38 【问题描述】:

使用 Ramda.js(和镜头),我想修改下面的 javascript 对象,将 ID=“/1/B/i”的对象的“NAME:VERSION1”更改为“NAME:VERSION2”。

我想使用镜头,因为我只想更改一个深度嵌套的值,但保持整个结构不变。

我不想使用 lensIndex,因为我不知道数组的顺序是什么,所以我想通过查找数组中的“id”字段来“找到”数组中的对象。

我可以用镜头做到这一点,还是应该用不同的方式做到这一点?


  "id": "/1",
  "groups": [
    
      "id": "/1/A",
      "apps": [
        
          "id": "/1/A/i",
          "more nested data skipped to simplify the example":  
        
      ]
    ,
    
      "id": "/1/B",
      "apps": [
         "id": "/1/B/n", "container":  ,
        
          "id": "/1/B/i",

          "container": 
            "docker": 
              "image": "NAME:VERSION1",
              "otherStuff": 
            
          
        
      ]
    

  ]

【问题讨论】:

【参考方案1】:

这应该可以通过创建一个通过 ID 匹配对象的镜头来实现,然后可以与其他镜头组合以深入到图像场。

首先,我们可以创建一个镜头,该镜头将专注于与某个谓词匹配的数组元素(注意:如果保证至少匹配列表中的一个元素,这将是一个有效的镜头)

//:: (a -> Boolean) -> Lens [a] a
const lensMatching = pred => (toF => entities => 
    const index = R.findIndex(pred, entities);
    return R.map(entity => R.update(index, entity, entities),
                 toF(entities[index]));
);

请注意,我们在这里手动构建镜头,而不是使用R.lens 来避免重复查找与谓词匹配的项目的索引。

一旦我们有了这个函数,我们就可以构建一个与给定 ID 匹配的镜头。

//:: String -> Lens [ id: String ]  id: String 
const lensById = R.compose(lensMatching, R.propEq('id'))

然后我们可以将所有镜头组合在一起以瞄准像场

const imageLens = R.compose(
  R.lensProp('groups'),
  lensById('/1/B'),
  R.lensProp('apps'),
  lensById('/1/B/i'),
  R.lensPath(['container', 'docker', 'image'])
)

可用于更新data 对象,如下所示:

set(imageLens, 'NAME:VERSION2', data)

如果您愿意,您可以更进一步,并声明一个专注于图像字符串版本的镜头。

const vLens = R.lens(
  R.compose(R.nth(1), R.split(':')),
  (version, str) => R.replace(/:.*/, ':' + version, str)
)

set(vLens, 'v2', 'NAME:v1') // 'NAME:v2'

然后可以将其附加到 imageLens 的组合中,以定位整个对象中的版本。

const verLens = compose(imageLens, vLens);
set(verLens, 'VERSION2', data);

【讨论】:

这真的很容易理解,并且可以很容易地组合和/或修改。谢谢!对于 lensMatching,可以将其替换为:function lensMatching(pred) return R.lens( R.find(pred), (newVal, array, other) => const index = R.findIndex(pred, array); return R.update(index, newVal, array); ) 这对我来说似乎更容易与镜头文档相关联。但是,我错过了什么吗? @GregEdwards 这也应该可以。我建议其他实现的主要原因是防止扫描数组两次(一次在find 和一次在findIndex),但是如果数组相当小,这应该不是问题。 感谢您的解决方案,@ScottChristopher :) 我对 ramda 和函数式编程完全陌生,但这不是 ramda 中缺少的功能吗? – 按属性值匹配镜头?我认为这是一个非常常见的场景,并且希望能够直接将最终的 compose-function 编写为这样的数组:const imageLens = R.lensPath(['groups', id: '/1/B', 'apps', id: '/1/B/i', 'container', 'docker', 'image']) @aweibell 有很多镜头(以及其他功能)可以使用 Ramda 提供的原语构建,这些原语未包含在库中。使用像提议的镜头这样的镜头有点棘手,因为您需要确保镜头始终匹配它们的焦点,这是通用库无法保证的(例如,它与空阵列匹配什么?) @aweibell 没错,像R.lensIndex 这样的函数属于类似的类别,尽管它们的有用性被认为超过了非全面性的潜在风险。如果你对这个功能有类似的感觉,那么我建议在 ramda/ramda github repo 上提出问题或 PR,以便向更广泛的 Ramda 社区提出这个想法。【参考方案2】:

这里有一个解决方案:

const updateDockerImageName =
R.over(R.lensProp('groups'),
       R.map(R.over(R.lensProp('apps'),
                    R.map(R.when(R.propEq('id', '/1/B/i'),
                                 R.over(R.lensPath(['container', 'docker', 'image']),
                                        R.replace(/^NAME:VERSION1$/, 'NAME:VERSION2')))))));

当然,这可以分解为更小的函数。 :)

【讨论】:

有没有办法不让查询嵌套得这么深?使用“撰写”还是...? 感谢您的回答,让我们更清楚如何使用 over、map 和 when nice。

以上是关于Ramda js:具有嵌套对象数组的深度嵌套对象的镜头的主要内容,如果未能解决你的问题,请参考以下文章

markdown 使用ramda按嵌套值过滤对象数组

markdown 使用ramda通过嵌套值过滤对象数组:有时您无权访问后端,并且您希望过滤来自

对象数组 深度复制,支持对象嵌套数组数组嵌套对象

在 ES6 深度嵌套的对象的 javascript 数组中查找值

具有对象元素的嵌套数组模式返回具有空对象的数组

遍历具有深层嵌套对象和数组的对象数组