【问题标题】:Using ramda.js, how to replace a value in a nested structure?使用 ramda.js,如何替换嵌套结构中的值?
【发布时间】:2022-02-09 04:25:23
【问题描述】:

我正在尝试利用technique shown here 将对象中的值替换为ramda.js。与链接引用不同,我的对象有更多的嵌套层,因此它失败了。

在以下示例中,我们有一个对象详细说明了城市中的景点。首先它指定了城市,我们进入nyc,然后进入zoos,然后是StatenIslandZoo,最后我们到达zooInfo,它保存着两只动物的两条记录。在每一个中,我们在与 animal 键关联的值中都有 aniaml 的名称。我想通过用另一个字符串替换它来更正值的字符串并返回整个 cityAttractions 对象的新副本。

const cityAttractions = {
    "cities": {
        "nyc": {
            "towers": ["One World Trade Center", "Central Park Tower", "Empire State Building"],
            "zoos": {
                "CentralParkZoo": {},
                "BronxZoo": {},
                "StatenIslandZoo": {
                    "zooInfo": [
                        {
                            "animal": "zebra_typo", // <- replace with "zebra"
                            "weight": 100
                        },
                        {
                            "animal": "wrongstring_lion", // <- replace with "lion"
                            "weight": 1005
                        }
                    ]
                }
            }
        },
        "sf": {},
        "dc": {}
    }
}

所以我定义了一个和this one很相似的函数:

const R = require("ramda")

const myAlter = (myPath, whereValueEquals, replaceWith, obj) => R.map(
    R.when(R.pathEq(myPath, whereValueEquals), R.assocPath(myPath, replaceWith)),
    obj
)

然后调用myAlter()并将输出存储到altered

const altered = myAlter(["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo", "animal"], "zebra_typo", "zebra", cityAttractions)

但是在检查时,我意识到没有发生任何替换:

console.log(altered.cities.nyc.zoos.StatenIslandZoo.zooInfo)
// [
//   { animal: 'zebra_typo', weight: 100 },
//   { animal: 'wrongstring_lion', weight: 1005 }
// ]

一些疑难解答
如果我们返回并检查原始的cityAttractions 对象,那么我们可以首先仅提取cityAttractions.cities.nyc.zoos.StatenIslandZoo.zooInfo 的级别,然后使用myAlter() 对其进行操作。

const ZooinfoExtraction = R.path(["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo"])(cityAttractions)
console.log(ZooinfoExtraction)
// [
//   { animal: 'zebra_typo', weight: 100 },
//   { animal: 'wrongstring_lion', weight: 1005 }
// ]

console.log(myAlter(["animal"], "zebra_typo", "zebra", ZooinfoExtraction))
// here it works!
// [
//   { animal: 'zebra', weight: 100 },
//   { animal: 'wrongstring_lion', weight: 1005 }
// ]

因此,出于某种原因,myAlter() 适用于提取的 ZooinfoExtraction,但不适用于原始 cityAttractions。这是一个问题,因为我需要整个原始结构(只需替换指定的值)。


编辑 - 疑难解答 2


我想问题在于

R.path(["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo", "animal"], cityAttractions)

返回undefined

【问题讨论】:

    标签: javascript ramda.js


    【解决方案1】:

    主要问题是animal 属性是数组项的一部分。由于数组索引应该是一个数字,所以 Zebra 的路径实际上是:

    ["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo", 0, "animal"]
    

    但是,这将迫使您知道实际的索引。

    此外,映射数组会返回数组的克隆(包含更改),而不是整个结构。

    要解决此问题,您可以使用带有R.over 的镜头(在本例中为R.lensPath)返回整个结构的更新克隆。

    例子:

    const { curry, over, lensPath, map, when, pathEq, assoc } = R
      
    const alterAnimal = curry((path, subPath, whereValueEquals, replaceWith, obj) =>
      over(
        lensPath(path), 
        map(when(pathEq(subPath, whereValueEquals), assoc(subPath, replaceWith))),
        obj
      ))
    
    const cityAttractions = {"cities":{"nyc":{"towers":["One World Trade Center","Central Park Tower","Empire State Building"],"zoos":{"CentralParkZoo":{},"BronxZoo":{},"StatenIslandZoo":{"zooInfo":[{"animal":"zebra_typo","weight":100},{"animal":"wrongstring_lion","weight":1005}]}}},"sf":{},"dc":{}}}
    
    const altered = alterAnimal(
      ["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo"],
      ["animal"],
      "zebra_typo", 
      "zebra", 
      cityAttractions
    )
    
    console.log(altered)
    .as-console-wrapper {max-height: 100% !important; top: 0}
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;

    由于你转换了一个对象的属性值,你也可以使用R.evolve,并提供一个可以覆盖所有情况的更新函数。例如:

    const { curry, over, lensPath, map, evolve, flip, prop, __ } = R
      
    const alterObj = curry((updateFn, prop, path, obj) =>
      over(
        lensPath(path), 
        map(evolve({
          [prop]: updateFn
        })),
        obj
      ))
      
    const replacements = {
      'zebra_typo': 'zebra',
      'wrongstring_lion': 'lion',
    }
      
    const alterAnimals = alterObj(prop(__, replacements))
    
    const cityAttractions = {"cities":{"nyc":{"towers":["One World Trade Center","Central Park Tower","Empire State Building"],"zoos":{"CentralParkZoo":{},"BronxZoo":{},"StatenIslandZoo":{"zooInfo":[{"animal":"zebra_typo","weight":100},{"animal":"wrongstring_lion","weight":1005}]}}},"sf":{},"dc":{}}}
    
    const altered = alterAnimals(
      "animal",
      ["cities", "nyc", "zoos", "StatenIslandZoo", "zooInfo"],
      cityAttractions
    )
    
    console.log(altered)
    .as-console-wrapper {max-height: 100% !important; top: 0}
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;

    【讨论】:

      【解决方案2】:

      使用镜头

      另一种基于lens 的方法是编写一个新的lens 函数。 Ramda 仅提供 lensProplensIndexlensPath。但是我们可以写一个匹配animal 匹配的第一个数组元素。我可以重用我在其他答案中使用过的lensMatch 函数,然后使用const animalLens = lensMatch ('animal') 对其进行配置。然后我们可以将其与其他镜头组合,以获得我们想要更改的属性。它可能看起来像这样:

      const lensMatch = (propName) => (key) => lens ( 
        find (propEq (propName, key)),
        (val, arr, idx = findIndex (propEq (propName, key), arr)) =>
            update (idx > -1 ? idx : length (arr), val, arr)
      )
      
      const animalLens = lensMatch ('animal')
      
      const updateAnimalName = (oldName, newName, attractions) =>  set (compose (
        lensPath (['cities', 'nyc', 'zoos', 'StatenIslandZoo', 'zooInfo']), 
        animalLens (oldName), 
        lensProp ('animal')
      ), newName, attractions)
      
      const cityAttractions = {cities: {nyc: {towers: ["One World Trade Center", "Central Park Tower", "Empire State Building"], zoos: {CentralParkZoo: {}, BronxZoo: {}, StatenIslandZoo: {zooInfo: [{animal: "zebra_typo", weight: 100}, {animal: "wrongstring_lion", weight: 1005}]}}}, sf: {}, dc: {}}}
      
      console .log (
        updateAnimalName ('zebra_typo', 'zebra', cityAttractions)
      )
      .as-console-wrapper {max-height: 100% !important; top: 0}
      <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
      <script> const {lens, find, propEq, findIndex, update, length, set, lensPath, compose, lensProp} = R </script>

      显然,如果您愿意,我们可以将其折叠到多个动物名称(例如斑马和狮子)上。

      通用替换函数

      另一种完全不同的方法是——如果错字值不太可能出现在数据结构的其他地方——简单地遍历整个树,将所有"zebra_typo" 替换为"zebra"。那将是一个简单的递归:

      const replaceVal = (oldVal, newVal) => (o) =>
        o == oldVal 
          ? newVal
        : Array .isArray (o)
          ? o .map (replaceVal (oldVal, newVal))
        : Object (o) === o
          ? Object .fromEntries (Object .entries (o) .map (([k, v]) => [k, replaceVal (oldVal, newVal) (v)]))
        : o
      
      
      const cityAttractions = {cities: {nyc: {towers: ["One World Trade Center", "Central Park Tower", "Empire State Building"], zoos: {CentralParkZoo: {}, BronxZoo: {}, StatenIslandZoo: {zooInfo: [{animal: "zebra_typo", weight: 100}, {animal: "wrongstring_lion", weight: 1005}]}}}, sf: {}, dc: {}}}
      
      console .log (
        replaceVal ('zebra_typo', 'zebra') (cityAttractions)
      )
      .as-console-wrapper {max-height: 100% !important; top: 0}

      这种方法非常通用,但更针对将所有“foo”值替换为“bar”值,而不管级别如何。但它可能适用于您的情况。

      【讨论】:

        猜你喜欢
        • 2020-11-17
        • 2020-12-02
        • 1970-01-01
        • 1970-01-01
        • 2020-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多