web-dev-qa-db-fra.com

Ramda js: lentille pour des objets profondément imbriqués avec des tableaux d'objets imbriqués

En utilisant Ramda.js (et les lentilles), je veux modifier l'objet JavaScript ci-dessous pour changer "NAME: VERSION1" en "NAME: VERSION2" pour l'objet qui a ID = "/ 1/B/i".

Je souhaite utiliser un objectif car je souhaite simplement modifier une valeur profondément imbriquée, mais sinon, conserver la structure entière inchangée.

Je ne veux pas utiliser lensIndex parce que je ne sais jamais dans quel ordre seront les tableaux, donc à la place, je veux "trouver" l'objet dans un tableau en recherchant ses champs "id".

Puis-je le faire avec des lentilles, ou dois-je le faire différemment?

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

  ]
}
19
Greg Edwards

Cela devrait être possible en créant une lentille qui correspond à un objet par ID qui peut ensuite être composée avec d'autres lentilles pour descendre dans le champ d'image.

Pour commencer, nous pouvons créer un objectif qui se concentrera sur un élément d'un tableau qui correspond à un prédicat (remarque: ce ne sera un objectif valide que s'il est garanti qu'il correspond à au moins un élément de la liste)

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

Notez que nous construisons manuellement l'objectif ici plutôt que d'utiliser R.lens pour éviter la duplication de la recherche de l'index de l'élément qui correspond au prédicat.

Une fois que nous avons cette fonction, nous pouvons construire une lentille qui correspond à un ID donné.

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

Et puis nous pouvons composer tous les objectifs ensemble pour cibler le champ d'image

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

Qui peut être utilisé pour mettre à jour l'objet data comme ceci:

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

Vous pouvez ensuite aller plus loin si vous le souhaitez et déclarer un objectif qui se concentre sur la version de la chaîne d'image.

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

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

Cela pourrait ensuite être ajouté à la composition de imageLens pour cibler la version dans l'objet entier.

const verLens = compose(imageLens, vLens);
set(verLens, 'VERSION2', data);
25
Scott Christopher

Voici une solution:

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

Bien sûr, cela pourrait être décomposé en fonctions plus petites. :)

9
davidchambers