Ramda js: объектив для глубоко вложенных объектов с вложенными массивами объектов

Используя Ramda.js(и линзы), я хочу изменить объект JavaScript ниже, чтобы изменить "NAME: VERSION1" на "NAME: VERSION2" для объекта с ID = "/1/B/i".

Я хочу использовать объектив, потому что хочу просто изменить одно глубоко вложенное значение, но в остальном сохранить всю структуру без изменений.

Я не хочу использовать lensIndex, потому что я никогда не знаю, в каком порядке будут массивы, поэтому вместо этого я хочу "найти" объект в массиве, ища его поля "id".

Могу ли я сделать это с линзами, или я должен делать это по-другому?

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

  ]
}

Ответы

Ответ 1

Это должно быть возможно, создав объектив, который соответствует объекту по идентификатору, который затем может быть скомпонован с другими объективами, чтобы развернуться к полю изображения.

Для начала мы можем создать объектив, который будет фокусироваться на элементе массива, который соответствует некоторому предикату (обратите внимание: это будет только действительный объектив, если он будет соответствовать как минимум одному элементу списка)

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

Обратите внимание, что мы вручную создаем объектив здесь, а не используя R.lens, чтобы сохранить дублирование поиска индекса элемента, соответствующего предикату.

Как только мы получим эту функцию, мы можем построить объектив, соответствующий данному ID.

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

И тогда мы сможем собрать все линзы вместе, чтобы направить поле изображения

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

Что можно использовать для обновления объекта data следующим образом:

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

Затем вы можете сделать это еще дальше, если хотите и объявите объектив, который фокусируется на версии строки изображения.

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

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

Затем он может быть добавлен в состав imageLens для таргетинга версии внутри всего объекта.

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

Ответ 2

Здесь одно решение:

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

Это может быть разложено на более мелкие функции, конечно.:)