简体   繁体   English

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

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

I'm trying to leverage the technique shown here for replacing values in an object with ramda.js .我正在尝试利用此处显示的技术将 object 中的值替换为ramda.js Unlike the linked reference, my object has many more nesting layers, and so it fails.与链接参考不同,我的 object 有更多的嵌套层,所以它失败了。

In the following example, we have an object that details attractions in cities.在下面的示例中,我们有一个 object 详细说明了城市中的景点。 First it specifies the cities, the we dive in into nyc , then to zoos , then StatenIslandZoo , and finally we get to zooInfo that holds two records for two animals.首先它指定了城市,我们深入到nyc ,然后是StatenIslandZoo zoos最后我们到达zooInfo ,它保存了两种动物的两条记录。 In each one, we have the aniaml's name in the value associated with the animal key.在每一个中,我们在与animal键关联的值中都有动物的名称。 I want to correct the value's string by replacing it with another string and return a new copy of the entire cityAttractions object.我想通过将其替换为另一个字符串来更正该值的字符串,并返回整个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": {}
    }
}

So I defined a function very similar to this one :所以我定义了一个与这个非常相似的 function :

const R = require("ramda")

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

And then called myAlter() and stored the ouput into altered :然后调用myAlter()并将输出存储到altered中:

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

But when checking, I realize no replacement had happend:但是在检查时,我意识到没有发生替换:

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

Some troubleshooting一些故障排除
If I we go back and examine the original cityAttractions object, then we can first extract just the level of cityAttractions.cities.nyc.zoos.StatenIslandZoo.zooInfo , then acting on that with myAlter() does work.如果我们返回 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 }
// ]

So for some reason, myAlter() works on the extracted ZooinfoExtraction but not on the original cityAttractions .因此,出于某种原因, myAlter()适用于提取的ZooinfoExtraction但不适用于原始的cityAttractions Which is a problem because I need the entire original structure (just replacing the specified values).这是一个问题,因为我需要整个原始结构(只是替换指定的值)。


EDIT - troubleshooting 2编辑 -故障排除 2


I guess the problem relies in the fact that我想问题在于

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

returns undefined .返回undefined

There main problem is that the animal property is part of an array item.主要问题是animal属性是数组项的一部分。 Since array index should be a number, the path for Zebra is actually:由于数组索引应该是一个数字,所以 Zebra 的路径实际上是:

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

However, this will force you to know the actual index.但是,这将迫使您知道实际索引。

In addition, mapping an array returns a clone of the array (with the changes), and not the entire structure.此外,映射一个数组会返回数组的克隆(带有更改),而不是整个结构。

To solve this problem, you can use a lens ( R.lensPath in this case) with R.over to return an updated clone of the entire structure.要解决此问题,您可以使用带有R.lensPath的镜头(在本例中为R.over )以返回整个结构的更新克隆。

Example:例子:

 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>

Since you transform an object's property value, you can also use R.evolve , and supply an update function that can cover all cases.由于您转换对象的属性值,您还可以使用R.evolve ,并提供可以涵盖所有情况的更新 function。 For example:例如:

 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>

Using Lenses使用镜头

Another lens -based approach is to write a new lens function. Ramda only supplies lensProp , lensIndex and lensPath .另一种基于lens的方法是编写一个新lens function。Ramda 仅提供lensProplensIndexlensPath But we could write one that matches the first array element where the animal matches.但是我们可以编写一个匹配animal匹配的第一个数组元素的代码。 I can reuse a lensMatch function I've used in other answers and then configure it with const animalLens = lensMatch ('animal') .我可以重复使用我在其他答案中使用过的lensMatch function,然后使用const animalLens = lensMatch ('animal')对其进行配置。 Then we can compose that with other lenses to get to the property we want to change.然后我们可以将其与其他镜头合成以获得我们想要更改的属性。 It might look like this:它可能看起来像这样:

 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>

Obviously we could then fold this over multiple animal names (say zebra and lion) if you wanted.显然,如果您愿意,我们可以将其折叠到多个动物名称(比如斑马和狮子)上。

A generic replacement function通用替代品 function

Another entirely different approach would be -- if the typo values are unlikely to appear elsewhere in your data structure -- to simply walk the whole tree, replacing all "zebra_typo" with "zebra" .另一种完全不同的方法是——如果拼写错误值不太可能出现在数据结构中的其他地方——简单地遍历整棵树,将所有"zebra_typo"替换为"zebra" That would be a simple recursion:那将是一个简单的递归:

 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}

This approach is quite generic, but is more targeted at replacing all "foo" values with "bar" ones, regardless of level.这种方法非常通用,但更针对于将所有“foo”值替换为“bar”值,而不管级别如何。 But it might work for your case.但它可能适用于您的情况。

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

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