使用ramda修改对象数组

时间:2018-07-13 07:25:02

标签: ramda.js

我有一个如下所示的对象数组,

[
{      
  "myValues": []
},
{
 "myValues": [],
 "values": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],
  "availableValues": [],      
  "selectedValues": []
}
]

如果我迭代该对象,则该对象中存在的“值”键也会将其转换为以下形式,

[
{      
  "myValues": []
},
{
  "myValues": [],
  "values": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],
  "availableValues": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],      
  "selectedValues": ["x", "y"]
}
]

我尝试了ramda函数编程,但没有结果,

let findValues = x => {
  if(R.has('values')(x)){
  let availableValues = R.prop('values')(x);
  let selectedValues = R.pluck('a')(availableValues);
  R.map(R.evolve({
    availableValues: availableValues,
    selectedValues: selectedValues
  }))
 }
}
R.map(findValues, arrObj);

2 个答案:

答案 0 :(得分:2)

R.evolve允许您通过将对象的值分别传递到提供的对象中的相应函数来更新对象的值,尽管它不允许您引用其他属性的值。

但是,R.applySpec类似地接受一个函数对象并返回一个新函数,该函数会将其参数传递给该对象中提供的每个函数以创建一个新对象。这将允许您引用周围对象的其他属性。

我们还可以使用R.when来仅在满足给定谓词的情况下应用某些转换。

const input = [
  {      
    "myValues": []
  },
  {
    "myValues": [],
    "values": [
      {"a": "x", "b": "1"},
      {"a": "y", "b": "2"}
    ],
    "availableValues": [],      
    "selectedValues": []
  }
]

const fn = R.map(R.when(R.has('values'), R.applySpec({
  myValues:        R.prop('myValues'),
  values:          R.prop('values'),
  availableValues: R.prop('values'),
  selectedValues:  R.o(R.pluck('a'), R.prop('values'))
})))

const expected = [
  {      
    "myValues": []
  },
  {
    "myValues": [],
    "values": [
      {"a": "x", "b": "1"},
      {"a": "y", "b": "2"}
    ],
    "availableValues": [
      {"a": "x", "b": "1"},
      {"a": "y", "b": "2"}
    ],      
    "selectedValues": ["x", "y"]
  }
]

console.log(R.equals(fn(input), expected))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

答案 1 :(得分:2)

不可移植性

首先,您需要小心动词。 Ramda不会修改您的数据结构。不变性对于Ramda以及整个函数式编程至关重要。如果您实际上正在寻找对数据进行变异的方法,Ramda将不是您的工具包。但是,Ramda会对其进行转换,并且可以以相对内存友好的方式进行转换,重用数据结构中未转换的部分。

修正方法

让我们首先来看一下清理函数中的几个问题:

let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
}

第一个最明显的问题是该函数不返回任何内容。如上所述,Ramda不会突变您的数据。因此,提供给Ramda的map时,不返回内容的函数是无用的。我们可以通过返回map(evolve)调用的结果并在if条件失败时返回原始值来解决此问题:

let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
  return x;
}

接下来,map调用没有任何意义。 evolve已经迭代了对象的属性。所以我们可以删除它。但是我们还需要将evolve应用于您的输入值:

let findValues = x => {
  if (R.has('values')(x)){
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }, x)
  }
  return x;
}

还有另一个问题。 Evolve需要一个包含转换值的函数的对象。例如,

evolve({
  a: n => n * 10,
  b: n => n + 5
})({a: 1, b: 2, c: 3}) //=> {a: 10, b: 7, c: 3}

请注意,提供给evolve的对象中的属性值是函数。该代码提供值。我们可以通过availableValues: R.always(availableValues)R.always包装这些值,但我认为使用带有参数“ _”的lambda更简单,这表明该参数并不重要:{{ 1}}。您也可以编写availableValues: _ => availableValues,但是下划线表明了通常的值会被忽略的事实。

修复此问题后,该函数将起作用:

availableValues: () => available values
let findValues = x => {
  if (R.has('values')(x)){
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.evolve({
      availableValues: _ => availableValues,
      selectedValues: _ => selectedValues
    }, x)
  }
  return x;
}

let arrObj = [{myValues: []}, {availableValues: [], myValues: [], selectedValues: [], values: [{a: "x", b: "1"}, {a: "y", b: "2"}]}];

console.log(R.map(findValues, arrObj))

如果您在此处运行此代码段,您还将看到一个有趣的地方,即所得的<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>属性是对原始availableValues的引用,而不是其单独的副本。这有助于表明Ramda正在重用原始数据。您也可以通过注意values来看到这一点。

其他方法

我写了我自己的版本,而不是从你的版本开始。这是一个工作功能:

R.map(findValues, arrObj)[0] === arrObj[0] //=> true

请注意when的用法,它捕获了“当此谓词对您的值正确时,请使用以下转换;否则请使用原始值”的概念。我们通过谓词const findValues = when( has('values'), x => evolve({ availableValues: _ => prop('values', x), selectedValues: _ => pluck('a', prop('values', x)) }, x) ) ,与上面的基本相同。我们的转换与您使用has('values')的转换类似,但是它跳过了临时变量,而重复执行prop调用的花费很小。

这个版本仍然让我感到困扰。将这些lambda与“ evolve”一起使用实际上是对_的滥用。我认为这更干净:

evolve

这里我们使用const findValues = when( has('values'), x => merge(x, { availableValues: prop('values', x), selectedValues: pluck('a', prop('values', x)) }) ) (它是Object.assign的纯函数版本),将第二个对象的参数添加到第一个对象的参数中,并在它们冲突时替换。

这就是我会选择的。

其他解决方案

自从我开始写这个答案以来,斯科特·克里斯托弗(Scott Christopher)基本上发布了另一个答案

merge

这是一个很好的解决方案。它也很好地没有点。如果您的输入对象固定为这些属性,那么我将在我的const findValues = R.map(R.when(R.has('values'), R.applySpec({ myValues: R.prop('myValues'), values: R.prop('values'), availableValues: R.prop('values'), selectedValues: R.o(R.pluck('a'), R.prop('values')) }))) 版本中选择该对象。如果您的实际对象包含更多属性,或者只有在输入中包含动态属性时才应将它们包含在输出中,那么我将选择merge解决方案。关键是斯科特的解决方案对于此特定结构更清洁,但我的解决方案可以更好地扩展到更复杂的对象。

分步

这不是一个非常复杂的转换,但它并不简单。构建此类内容的一种简单方法是在非常小的阶段中进行构建。我经常在Ramda REPL中执行此操作,以便随时查看结果。

我的过程看起来像这样:

步骤1

确保只正确转换正确的值:

merge

步骤2

确保为我的转换器函数指定了正确的值。

const findValues = when(
  has('values'), 
  always('foo')
)
map(findValues, arrObj) //=> [{myValues: []}, "foo"]

这里identitykeys一样好。任何证明正确对象已传递的东西都可以。

步骤3

测试const findValues = when( has('values'), keys ) map(findValues, arrObj) //=> [{myValues: []}, ["myValues", "values", "availableValues", "selectedValues"]] 在这里是否可以正常工作。

merge

所以我可以正确合并两个对象。请注意,这保留了原始对象的所有现有属性。

步骤4

现在,实际上转换我想要的第一个值:

const findValues = when(
  has('values'), 
  x => merge(x, {foo: 'bar'})
)
map(findValues, arrObj) 
//=> [
//     {myValues: []}, 
//     {
//       myValues: [], 
//       values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       availableValues: [], 
//       selectedValues: [], 
//       foo: "bar"
//     }
//   ]

步骤5

这就是我想要的。因此,现在添加另一个属性:

const findValues = when(
  has('values'),
  x => merge(x, {
    availableValues: prop('values', x)
  })
)
map(findValues, arrObj) 
//=> [
//     {myValues: []}, 
//     {
//       myValues: [], 
//       values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       availableValues: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       selectedValues: []
//     }
//   ]

这终于给了我想要的结果。

这是一个快速的过程。我可以在REPL中非常快速地运行和测试,迭代我的解决方案,直到得到满足我要求的功能。