繁体   English   中英

使用 Ramda 或 Lodash 将带有数组的对象转换为对象数组

[英]Convert object with arrays to array of objects using Ramda or Lodash

示例输入:

const obj = {
  paths: ['path1', 'path2'],
  thumbnails: ['thumb1', 'thumb2'],
  sizes: [ // may not always be presented
    [100, 200],
    [120, 220],
  ],
};

预期输出:

const result = [
  {
    path: 'path1',
    thumbnail: 'thumb1',
    size: [100, 200],
  },
  {
    path: 'path2',
    thumbnail: 'thumb2',
    size: [120, 220],
  },
];

奖励积分:

const result1 = [
  {
    path: 'path1',
    thumbnail: 'thumb1',
    width: 100,
    height: 200,
  },
  {
    path: 'path2',
    thumbnail: 'thumb2',
    width: 120,
    height: 220,
  },
];

// without sizes

const result2 = [
  {
    path: 'path1',
    thumbnail: 'thumb1',
  },
  {
    path: 'path2',
    thumbnail: 'thumb2',
  },
];

我将如何使用 Ramda 或 Lodash 实现这一目标?

更新的答案

在这里,我们将我们关心的字段提取到数组中,将其转换为多维数组(记录数乘以数组中的元素数),转置该数组,然后将其压缩为对象。

我们使用相同的字段名称列表进行提取和重构对象。 它看起来像这样:

 const inflate = (names) => (obj) => map (zipObj (names)) (transpose (props (names) (obj))) const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} console .log (inflate (['paths', 'thumbnails', 'sizes']) (obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script> <script> const {map, zipObj, transpose, props, paths} = R </script>

对于您的扩展版本,只需在结果上运行后续map

const inflate = (names) => (obj) =>  
  map (zipObj (names)) (transpose (props (names) (obj)))
    .map (({sizes: [width, height], ...rest}) => ({...rest, width, height}))

基于规范的方法

Ori Drori 的回答挑战我找到一个基于规范的版本,让我们声明paths变为path并且sizes的第一个元素变为width 这是该想法的一个相当讨厌的版本。 我没时间清理它,但您至少可以在此看到一个有趣想法的骨架:

 const regroup = (spec, names = uniq (values (spec) .map (x => x.split ('.') [0]))) => (obj) => map (applySpec (map (pipe (split ('.'), path)) (spec))) (map (zipObj (names)) (transpose (props (names) (obj)))) const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} const spec = {path: 'paths', thumbnail: 'thumbnails', width: 'sizes.0', height: 'sizes.1'} console .log (regroup (spec) (obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script> <script> const {uniq, values, map, applySpec, pipe, split, path, zipObj, transpose, props} = R </script>

原始答案

(我最初误读并认为您想要元素的笛卡尔积,而不是匹配的索引。我将其留在这里,因为它确实回答了我认为更有趣的问题。)

TLDR

下面我们讨论几种实现。 用 Ramda 编写的可配置的代码如下所示:

const inflate = pipe (map (unwind), pipeWith (chain))
// ...
inflate (['paths', 'thumbnails', 'sizes', 'sizes']) (obj)

一个强大但不太灵活的香草看起来像这样:

const inflate = (obj, _, __, field = Object .entries (obj) .find (([k, v]) => Array .isArray (v))) => 
  field ? field [1] .flatMap ((v) => inflate ({...obj, [field [0]]: v})) : obj
// ...
inflate (obj)

Ramda 实现

Ramda 有一个专门用于此的功能: unwind 它接受一个属性名称并返回一个值数组,该名称对应于数组中的每个元素。 然后,我们可以简单地将它们与chain一起管道,如下所示:

 const inflate = pipeWith (chain) ([ unwind ('paths'), unwind ('thumbnails'), unwind ('sizes'), unwind ('sizes') ]) const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} console .log (inflate (obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script> <script> const {pipeWith, chain, unwind} = R </script>

如果pipeWith不熟悉,这相当于

const inflate = pipe (
  unwind ('paths'),
  chain (unwind ('thumbnails')),
  chain (unwind ('sizes')),
  chain (unwind ('sizes'))
)

chain调用在这里的作用类似于Array.prototype.flatMap

更抽象的 Ramda 实现

但这需要进一步的抽象。 我们可以通过提取我们想要扩展的路径列表来使其更具声明性,这样我们就可以简单地调用inflate (['paths', 'thumbnails', 'sizes', 'sizes']) (obj) 这是一个实现:

 const inflate = pipe (map (unwind), pipeWith (chain)) const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} console .log (inflate (['paths', 'thumbnails', 'sizes', 'sizes']) (obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script> <script> const {pipe, map, pipeWith, chain, unwind} = R </script>

强大但不太灵活的香草实现

但也许我们不想指定字段。 如果我们只想提供对象并扩展所有数组,包括嵌套数组,那么我们可能想要编写一个递归版本。 在这一点上,Ramda 可能会分散注意力。 我们可以像这样编写一个简单的 vanilla JS 实现:

 const inflate = (obj, _, __, field = Object .entries (obj) .find (([k, v]) => Array .isArray (v))) => field ? field [1] .flatMap ((v) => inflate ({...obj, [field [0]]: v})) : obj const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} console .log (inflate (obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}

这很强大。 它适用于任何数组属性,即使它们是多维的。 但它是不灵活的。 您不能选择扩展某些数组属性。 这是好是坏取决于你的情况。

我们可以以同样的方式使用 Ramda。 这是使用 Ramda 函数的部分转换:

const inflate = (obj) => call (
  (field = pipe (toPairs, find (pipe (last, is(Array)))) (obj), [k, v] = field || []) =>
    field ? chain ((v) => inflate (assoc (k, v, obj))) (field [1]) : obj 
)

我们可以继续把东西拉出来,直到我们完全没有点,但我认为我们在这里会慢慢失去可读性,不像早期的 Ramda 版本,我们最终得到了相当优雅、可读的实现。

名称

这些版本(在原始或更新的答案中)都没有更改名称。 我们不会尝试将“sizes”转换为“size”或将“paths”转换为“path”。 如果您可以控制输入格式,我建议您只需将其切换到那里。 如果没有,我可能会将其作为最后一步。 您可以使用 Ramda 的applySpec或使用renameKeysCookbook中的 renameKeys 之类的东西来做到这一点。 虽然可以将此键重命名折叠到上述函数中,但我希望它不如简单地进行第二次传递。

您需要将旧键映射到新键,使用R.props使用旧键获取值,转置,然后使用新键( mappings对象的值)压缩回对象:

 const {pipe, props, keys, transpose, map, zipObj, values} = R const fn = mapping => pipe( props(keys(mapping)), // get an array props by keys order transpose, // transpose them map(zipObj(values(mapping))) // map back to an object with the same order ) const obj = {paths: ['path1', 'path2'], thumbnails: ['thumb1', 'thumb2'], sizes: [[100, 200], [120, 220]]} console.log(fn({ paths: 'path', thumbnails: 'thumbnail', sizes: 'size' })(obj))
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

您特别想仅使用 ramada 来执行此操作吗?

因为这可以用香草来实现,如下所示。

对于预期输出 -

obj.paths.map((e, i) => ({ path: obj.paths[i], thumbnail: obj.thumbnails[i], size: obj.sizes[i] }))

对于奖金输出 -

obj.paths.map((e, i) => ({ path: obj.paths[i], thumbnail: obj.thumbnails[i], width: obj.sizes[i][0], height: obj.sizes[i][1] }))

大小数组可以为空,即。 [] -

obj.paths.map((e, i) => ({ path: obj.paths[i], thumbnail: obj.thumbnails[i], width: obj.sizes[i] !== undefined ? obj.sizes[i][0] : '', height: obj.sizes[i] !== undefined ? obj.sizes[i][1] : ''}))

使用 vanilla Javascript,我们可以轻松处理索引,但 Ramda.js 没有它们,因此您可以使用R.forEachObjIndexed来处理您的情况。

const obj = {
  paths: ['path1', 'path2'],
  thumbnails: ['thumb1', 'thumb2'],
  sizes: [ // may not always be presented
    [100, 200],
    [120, 220],
  ],
};

const result = []
R.forEachObjIndexed((value, index) => {
  const currentData = {
    path: value,
  }
  if(obj.thumbnails && obj.thumbnails[index]) {
    currentData.thumbnail = obj.thumbnails[index]
  }
  if(obj.sizes && obj.sizes[index]) {
    const [width, height] = obj.sizes[index]
    currentData.width = width
    currentData.height = height
  }
  result.push(currentData)
}, obj.paths)

console.log(result)

你可以在这里的操场上试试

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM