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