[英]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 僅提供lensProp
、 lensIndex
和lensPath
。 但是我們可以編寫一個匹配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>
顯然,如果您願意,我們可以將其折疊到多個動物名稱(比如斑馬和獅子)上。
另一種完全不同的方法是——如果拼寫錯誤值不太可能出現在數據結構中的其他地方——簡單地遍歷整棵樹,將所有"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.