[英]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>
(我最初误读并认为您想要元素的笛卡尔积,而不是匹配的索引。我将其留在这里,因为它确实回答了我认为更有趣的问题。)
下面我们讨论几种实现。 用 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 有一个专门用于此的功能: 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
。
但这需要进一步的抽象。 我们可以通过提取我们想要扩展的路径列表来使其更具声明性,这样我们就可以简单地调用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
或使用renameKeys
的Cookbook中的 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.