2016-02-21 12 views
17

Utilizzando Ramda.js (e obiettivi), voglio modificare l'oggetto JavaScript qui sotto per cambiare "NAME: VERSION1" in "NAME: VERSION2" per l'oggetto che ha ID = "/ 1/B/i".Ramda js: obiettivo per oggetti profondamente annidati con matrici annidate di oggetti

Voglio utilizzare un obiettivo perché voglio solo modificare un valore profondamente annidato, ma altrimenti mantenere l'intera struttura invariata.

Non voglio usare lensIndex perché non so mai in quale ordine saranno gli array, quindi, invece, voglio "trovare" l'oggetto in un array cercando i suoi campi "id".

Posso farlo con obiettivi, o dovrei farlo in un modo diverso?

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

    ] 
} 

risposta

20

Ciò dovrebbe essere possibile creando un obiettivo che corrisponde a un oggetto per ID che può quindi essere composto con altri obiettivi per eseguire il drill-down sul campo dell'immagine.

Per cominciare, possiamo creare un obiettivo che si concentrerà su un elemento di un array che corrisponde ad un predicato (nota: questo sarà solo un obiettivo valido se è garantito che corrisponda ad almeno un elemento della lista)

//:: (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])); 
}); 

Nota che stiamo costruendo manualmente la lente qui piuttosto che utilizzare R.lens per salvare la duplicazione di trovare l'indice della voce che corrisponde al predicato.

Una volta ottenuta questa funzione, possiamo costruire un obiettivo che corrisponde a un determinato ID.

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

E poi siamo in grado di comporre tutte le lenti insieme di indirizzare il campo dell'immagine

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

che può essere utilizzato per aggiornare l'oggetto data in questo modo:

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

Si potrebbe quindi fare un ulteriore passo avanti se si desidera e dichiarare un obiettivo che si concentra sulla versione della stringa di immagine.

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

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

Questo potrebbe essere fatta utilizzando la composizione di imageLens per indirizzare la versione all'interno dell'intero oggetto.

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

Questo è veramente facile da capire e può essere facilmente composto e/o modificato. Grazie! Per lensMatching, potrebbe quello essere sostituito da: 'funzione lensMatching (pred) { ritorno R.lens ( R.find (pred), (newVal, matrice, altro) => { index = const R.findIndex (pred, array); return R.aggiornamento (index, newVal, array); } ) } Mi sembra un po 'più facile relazionarmi alla documentazione dell'obiettivo. Ma mi sto perdendo qualcosa? –

+0

@GregEdwards Anche questo dovrebbe funzionare. Il motivo principale per cui ho suggerito l'altra implementazione era di evitare la scansione degli array due volte (una volta in 'find' e una volta in' findIndex'), tuttavia questo non dovrebbe essere un problema se gli array sono ragionevolmente piccoli. –

+1

Grazie per la soluzione, @ScottChristopher :) Sono completamente nuovo per ramda, e la programmazione funzionale a tutti, ma non è questa una funzionalità mancante in ramda? - per far coincidere un obiettivo in base al valore della proprietà? Presumo che sia uno scenario abbastanza comune e preferirei essere in grado di scrivere direttamente la funzione di composizione finale direttamente come una matrice come questa: 'const imageLens = R.lensPath (['groups', {id: '/ 1/B '},' app ', {id:'/1/B/i '},' contenitore ',' finestra mobile ',' immagine ']) ' – aweibell

6

Ecco una soluzione:

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'))))))); 

Questo potrebbe essere scomposto in funzioni più piccole, naturalmente. :)

+0

C'è un modo per non avere la query così profondamente annidata? Usando "componi" o ...? –

+2

Grazie per la tua risposta, che rende più chiaro come utilizzare oltre, la mappa e quando bene. –

Problemi correlati