使用 Ramda 向 JSONobject 添加新的嵌套条目

Adding new nested entry to JSONobject using Ramda

有一个初始空对象,我们应该向其添加 json 数据。使用 lodash 可以如下添加。

const _ = require('lodash');
data = {};
_.set(data, 'client.firstName', incomingData.firstName ? incomingData.firstName : '');
_.set(data, 'client.lastName', incomingData.lastName ? incomingData.lastName : '');
_.set(data, 'client.middleInitial', incomingData.middleInitial ? incomingData.middleInitial : '');
_.set(data, 'maidenName', incomingData.maidenName ? incomingData.maidenName : '');
...

现在我应该使用 rambda 进行相同的实现。 通过参考 ramda 文档进行了尝试,但无法找到确切的解决方案,因此无法像这样接收输出:

{
"client": 
  {
   "firstName":"Pete",
   "lastName":"Cathy",
   "middleInitial":"M"
  },
"maidenName":"catty"
...
...
}

请建议我实现相同目标的正确实施方式。

使用 Ramda,您可以使用 R.applySpec 通过提供包含生成每个 属性 的函数的规范从另一个对象创建新对象。在您的情况下,使用 R.propOr 从原始对象中获取数据,并提供一个空字符串作为回退:

const { applySpec, propOr } = R;

const getData = applySpec({
  client: {
   firstName: propOr('', 'firstName'),
   lastName: propOr('', 'lastName'),
   middleInitial: propOr('', 'middleInitial')
  },
  maidenName: propOr('', 'maidenName')
});

const incomingData = {
  firstName: 'Pete',
  lastName: 'Cathy',
  maidenName: 'Catty',
};

const data = getData(incomingData);

console.log(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Rambda 的工作原理相同,但至少 UMD 似乎有一个错误。如果 R.propOr 提供给所有属性,函数 returns 一个空对象。如果您遇到错误,请将 propOr 调用中的一个调用作为 hack 来使其工作。

const { applySpec, propOr } = R;

const getData = applySpec({
  client: {
   firstName: x => propOr('', 'firstName', x), // <- stupid hack
   lastName: propOr('', 'lastName'),
   middleInitial: propOr('', 'middleInitial')
  },
  maidenName: propOr('', 'maidenName')
});

const incomingData = {
  firstName: 'Pete',
  lastName: 'Cathy',
  maidenName: 'Catty',
};

const data = getData(incomingData);

console.log(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rambda/6.9.0/rambda.umd.js" integrity="sha512-1npVFfj/soXclDC0ts7zenNSKArClG4bNYSpOLuE5ojVI7mXLLdkoRvXGejgOS1p/zORDKajYMxXw/Ia6vNulQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

我的第一个建议是,您不要假设 lodash 中的某些内容会出现在 Ramda 中,反之亦然。它们是具有不同目标和不同功能的独立库。它们如此重叠是因为 lodash(通过 Underscore)和 Ramda(直接)从许多函数式语言中获得灵感。但他们从不同的方向汲取灵感。

在 Ramda 中(免责声明:我是它的作者之一)我们会避免像那个片段那样的代码。但我们 可以 相当接近:

最直接的转换

我们可以写一些比较接近的东西:

const _set = (o, ps, v) => assocPath (split ('.') (ps), v, o)

const incomingData = {firstName: 'Wilma', lastName: 'Flintstone', maidenName: 'Slaghoople'}

let data = {}

data = _set (data, 'client.firstName', incomingData.firstName ? incomingData.firstName : '');
data = _set (data, 'client.lastName', incomingData.lastName ? incomingData.lastName : '');
data = _set (data, 'client.middleInitial', incomingData.middleInitial ? incomingData.middleInitial : '');
data = _set (data, 'maidenName', incomingData.maidenName ? incomingData.maidenName : '');

console .log (data)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {assocPath, split} = R </script>

最大的区别在于 data 的不断重新分配。 Ramda 不会改变输入数据;这是其核心原则之一。所以我们必须使用我们的新 _set 函数来 return 一个新对象,然后将 data 重置为结果。

不是 Ramda 的方式

但是 Ramda 用户不太可能编写那样的代码。 data 的连续突变是一种诅咒。而且它非常重复。我们想将那些重复的命令式块包装成更具声明性的内容。

使用 path-配置

第一种可能性是使用像

这样的配置
[
  ['client.firstName', 'firstName', ''],
  ['client.lastName',  'lastName', ''],
  ['client.middleInitial', 'middleInitial', ''],
  ['client.maidenName', 'maidenName', ''],
]

驱动我们的代码。我们可以在 Ramda 中以足够简单的方式将这些条目折叠到一个对象中:

const transform = curry ((config, incoming) => reduce (
  (obj, [to, from, dflt]) =>
    assocPath (split ('.') (to), defaultTo (dflt, path (split ('.') (from), incoming)), obj), 
  {}, config
))

const config = [
  ['client.firstName', 'firstName', ''],
  ['client.lastName',  'lastName', ''],
  ['client.middleInitial', 'middleInitial', ''],
  ['maidenName', 'maidenName', ''],
]

const incomingData = {firstName: 'Wilma', lastName: 'Flintstone', maidenName: 'Slaghoople'}

console .log (transform (config) (incomingData))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {curry, reduce, assocPath, split, defaultTo, path} = R </script>

在这里,我们在输入配置中使用点分隔路径来详细说明我们的数据来自何处和去往何处,并且我们为缺失值提供简单的默认值。

如果您已经以这种点分隔的路径格式获取数据,这种样式将非常有用。但这种格式对于 Ramda 来说并不是特别自然,它使用字符串和整数数组作为其路径。因此,虽然我将其作为简化练习,但我们可能更喜欢使用这样的配置:

const config = [
  [['client', 'firstName'], ['firstName'], ''],
  [['client', 'lastName'],  ['lastName'], ''],
  [['client', 'middleInitial'], ['middleInitial'], ''],
  [['maidenName'], ['maidenName'], ''],
]

内置于 Ramda 中

但是如果你是从头开始编写,而不是从那些点分隔的路径开始,Ramda 有一个内置函数,它仍然更具声明性,并且非常适合此类工作:函数 applySpec,它接受一个输入对象并通过应用一个结构化配置对象来构建一个新对象,该对象的叶子是来自输入数据的函数。 (我看到 Ori Drori 有一个几乎相同的代码的答案。我认为值得在这里重复,如果没有别的可以表明这是最有可能的 Ramda 问题答案。)

使用applySpec很简单:

const transform = applySpec ({
  client: {
    firstName: propOr ('', 'firstName'),
    lastName: propOr ('', 'lastName'),
    middleInitial: propOr ('', 'middleInitial')
  },
  maidenName: propOr ('', 'maidenName')
}) 

const incomingData = {firstName: 'Wilma', lastName: 'Flintstone', maidenName: 'Slaghoople'}

console .log (transform (incomingData))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {applySpec, propOr} = R </script>

这是很好的声明;我们的配置对象的结构类似于我们想要接收的输出。它的叶子上使用 propOr 的函数简单地描述了从我们的输入中获取数据的位置以及如果它丢失了该怎么办。 (如果我们的输入是嵌套的,我们可能会使用 pathOr。)

哪种技术?

这两种技术之间的选择将取决于路径列表,比如 'client.firstName' 是提前知道的还是提供给您的数据。提供的片段建议前者,因此,我会建议 applySpec 解决方案。但如果情况并非如此,并且这些路径可能作为数据提供,那么基于 reduce 的路径可能是最好的。