繁体   English   中英

使用 ramda.js,如何替换嵌套结构中的值?

[英]Using ramda.js, how to replace a value in a nested structure?

我正在尝试利用此处显示的技术将 object 中的值替换为ramda.js 与链接参考不同,我的 object 有更多的嵌套层,所以它失败了。

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

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": {}
    }
}

所以我定义了一个与这个非常相似的 function :

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 }
// ]

一些故障排除
如果我们返回 go 并检查原始的cityAttractions object,那么我们可以首先仅提取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

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

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

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

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

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

例子:

 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}
 <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"></script>

由于您转换对象的属性值,您还可以使用R.evolve ,并提供可以涵盖所有情况的更新 function。 例如:

 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}
 <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"></script>

使用镜头

另一种基于lens的方法是编写一个新lens function。Ramda 仅提供lensProplensIndexlensPath 但是我们可以编写一个匹配animal匹配的第一个数组元素的代码。 我可以重复使用我在其他答案中使用过的lensMatch function,然后使用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>

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

通用替代品 function

另一种完全不同的方法是——如果拼写错误值不太可能出现在数据结构中的其他地方——简单地遍历整棵树,将所有"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”值,而不管级别如何。 但它可能适用于您的情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM