2016-02-21 50 views
17

Korzystając z Ramda.js (i soczewek), chcę zmodyfikować obiekt JavaScript poniżej, aby zmienić "NAME: VERSION1" na "NAME: VERSION2" dla obiektu, który ma ID = "/ 1/B/i".Ramda js: obiektyw dla obiektów głęboko zagnieżdżonych z zagnieżdżonymi tablicami obiektów

Chcę użyć soczewki, ponieważ chcę po prostu zmienić jedną głęboko zagnieżdżoną wartość, ale w przeciwnym razie zachować całą strukturę bez zmian.

Nie chcę używać lensIndex, ponieważ nigdy nie wiem, w jakiej kolejności są tablice, więc zamiast tego chcę "znaleźć" obiekt w tablicy, szukając jego pól "id".

Czy mogę to zrobić z soczewkami, czy powinienem zrobić to w inny sposób?

{ 
    "id": "/1", 
    "groups": [ 
    { 
     "id": "/1/A", 
     "apps": [ 
     { 
      "id": "/1/A/i", 
      "more nested data skipped to simplify the example": {} 
     } 
     ] 
    }, 
    { 
     "id": "/1/B", 
     "apps": [ 
     { "id": "/1/B/n", "container": {} }, 
     { 
      "id": "/1/B/i", 

      "container": { 
      "docker": { 
       "image": "NAME:VERSION1", 
       "otherStuff": {} 
      } 
      } 
     } 
     ] 
    } 

    ] 
} 

Odpowiedz

20

Powinno to być możliwe poprzez stworzenie soczewki, która pasuje do obiektu za pomocą identyfikatora, który może następnie zostać skomponowany z innymi soczewkami, aby przejść do pola obrazu.

Na początek możemy stworzyć soczewkę, która skupi się na elemencie tablicy, który pasuje do jakiegoś predykatu (uwaga: to będzie tylko prawidłowy obiektyw, jeśli zagwarantuje się dopasowanie co najmniej jednego elementu listy)

//:: (a -> Boolean) -> Lens [a] a 
const lensMatching = pred => (toF => entities => { 
    const index = R.findIndex(pred, entities); 
    return R.map(entity => R.update(index, entity, entities), 
       toF(entities[index])); 
}); 

Należy pamiętać, że jesteśmy ręcznie konstruowaniu obiektywu tutaj zamiast używać R.lens zapisać powielania znalezienie indeks elementu, który pasuje do predykat.

Po uzyskaniu tej funkcji możemy skonstruować soczewkę pasującą do danego identyfikatora.

//:: String -> Lens [{ id: String }] { id: String } 
const lensById = R.compose(lensMatching, R.propEq('id')) 

A potem możemy skomponować wszystkie soczewki razem kierować pola zdjęcia

const imageLens = R.compose(
    R.lensProp('groups'), 
    lensById('/1/B'), 
    R.lensProp('apps'), 
    lensById('/1/B/i'), 
    R.lensPath(['container', 'docker', 'image']) 
) 

które mogą być wykorzystane do aktualizacji obiektu data tak:

set(imageLens, 'NAME:VERSION2', data) 

można było potem wykonaj krok dalej, jeśli chcesz i zadeklaruj obiektyw, który koncentruje się na wersji ciągów obrazów.

const vLens = R.lens(
    R.compose(R.nth(1), R.split(':')), 
    (version, str) => R.replace(/:.*/, ':' + version, str) 
) 

set(vLens, 'v2', 'NAME:v1') // 'NAME:v2' 

ten może być następnie dołączony do składu imageLens kierować wersji w obrębie całego obiektu.

const verLens = compose(imageLens, vLens); 
set(verLens, 'VERSION2', data); 
+1

Jest to bardzo łatwe do zrozumienia i można je łatwo skomponować i/lub zmodyfikować. Dziękuję Ci! Dla lensMatching, to może być zastąpiony przez: 'lensMatching funkcji (pred) { powrotu R.lens ( R.find (pred) (newval, tablica inne) => { indeksu const = R.findIndex (pred, array); return R.update (index, newVal, array); } ) } ' Wydaje mi się, że łatwiej jest mi odnieść się do dokumentacji obiektywu. Ale czy czegoś brakuje? –

+0

@GregEdwards To też powinno działać. Głównym powodem, dla którego zasugerowałem drugą implementację, było uniemożliwienie dwukrotnego skanowania tablic (raz w 'find' i once in' findIndex'), jednak nie powinno to stanowić problemu, jeśli tablice są stosunkowo małe. –

+1

Dziękuję za rozwiązanie, @ScottChristopher :) Jestem całkowicie nowy w ramdzie i funkcjonalnym programowaniu w ogóle, ale czy nie jest to brakująca funkcja w ramdzie? - aby obiektyw pasował do wartości nieruchomości? Zakładam, że jest to dość powszechny scenariusz i wolałbym móc napisać końcową funkcję komponowania bezpośrednio jako tablicę: 'const imageLens = R.lensPath (['groups', {id: '/ 1/B '},' apps ', {id:'/1/B/i '},' container ',' docker ',' image ']) ' – aweibell

6

Oto jedno rozwiązanie:

const updateDockerImageName = 
R.over(R.lensProp('groups'), 
     R.map(R.over(R.lensProp('apps'), 
        R.map(R.when(R.propEq('id', '/1/B/i'), 
           R.over(R.lensPath(['container', 'docker', 'image']), 
             R.replace(/^NAME:VERSION1$/, 'NAME:VERSION2'))))))); 

To może być rozłożona na mniejsze funkcje, oczywiście. :)

+0

Czy istnieje sposób, aby pytanie nie było tak głęboko zagnieżdżone? Używając "compose" lub ...? –

+2

Dziękuję za odpowiedź, która sprawia, że ​​jest ona bardziej przejrzysta w użyciu, mapowaniu i ładowaniu. –