2016-02-21 18 views
17

Ramda.js(およびレンズ)を使用して、以下のJavaScriptオブジェクトを変更して、そのオブジェクトの「NAME:VERSION1」を「NAME:VERSION2」に変更します。 ID = "/ 1/B/i"である。Ramda js:ネストされたオブジェクトの配列を持つネストされたオブジェクトのレンズ

深く入れ子になった値を変更するだけで、それ以外の場合は構造全体を変更せずに保持したいので、レンズを使用したいと思います。

配列がどのような順序になるのかわからないので、lensIndexを使用したくないので、その代わりに、 "id"フィールドを探してオブジェクトを配列内で見つけたいと思っています。

私はレンズでこれを行うことができますか、それとも別の方法で行う必要がありますか?

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

これはもちろん、小さな機能に分解することができます

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

    ] 
} 

答えて

20

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

そして、我々はそうのようなdataオブジェクトを更新するために使用することができ、画像フィールド

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

を対象とするために一緒にすべてのレンズを構成することができます

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); 
+1

これは本当に分かりやすく、簡単に構成および/または変更できます。ありがとうございました! '関数lensMatching(predは){ リターンR.lens( R.find(predが)、 (newValに、アレイ、その他)=> { CONSTインデックス= R.findIndex:lensMatchingため、それを置き換えることができます(pred、array); return R.更新(インデックス、newVal、配列); } ) } これは、レンズのドキュメントに関連するのが少し簡単です。しかし、私は何かを逃していますか? –

+0

@GregEdwardsでもうまくいくはずです。私が他の実装を提案した主な理由は、配列を2回スキャンしないようにすることでした( 'find'と' findIndex'で一度)。しかし、配列がかなり小さければ、問題ではありません。 –

+1

解決策、@ScottChristopherありがとう:)私はまったく新しいramdaと関数型プログラミングですが、これはramdaの機能が欠けていませんか? - プロパティ値でレンズを一致させるには?私はそれがかなり一般的なシナリオであると仮定し、最後の作成関数を次のような配列として直接書くことができます: 'const imageLens = R.lensPath(['groups'、{id: '/ 1/ – aweibell

6

ここでは一つの解決策です。 :)

+0

クエリを深くネストしない方法はありますか? 「作成」または...を使用していますか? –

+2

あなたの答えをありがとう、どのように上、マップ、うまく使用する方法をより明確になります。 –

関連する問題