簡體   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