web-dev-qa-db-ja.com

Ramda js:ネストされたオブジェクトの配列を持つ深くネストされたオブジェクトのレンズ

Ramda.js(およびレンズ)を使用して、ID = "/ 1/B/i"のオブジェクトの "NAME:VERSION1"を "NAME:VERSION2"に変更するために、以下のJavaScriptオブジェクトを変更したいと思います。

深くネストされた値を1つだけ変更し、それ以外は構造全体を変更せずに保持したいので、レンズを使用します。

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

  ]
}
19
Greg Edwards

これは、IDでオブジェクトに一致するレンズを作成することで可能になります。IDは、他のレンズと組み合わせて画像フィールドにドリルダウンできます。

まず、いくつかの述語に一致する配列の要素に焦点を合わせるレンズを作成できます(注:これは、リストの少なくとも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);
25

これが1つの解決策です。

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

もちろん、これはより小さな機能に分解できます。 :)

9
davidchambers