我正在学习Javascript中的函数式编程并使用Ramda。我有这个对象
var fieldvalues = { name: "hello there", mobile: "1234",
meta: {status: "new"},
comments: [ {user: "john", comment: "hi"},
{user:"ram", comment: "hello"}]
};
像这样转换:
{
comments.0.comment: "hi",
comments.0.user: "john",
comments.1.comment: "hello",
comments.1.user: "ram",
meta.status: "new",
mobile: "1234",
name: "hello there"
}
我试过这个Ramda源,它有效。
var _toDotted = function(acc, obj) {
var key = obj[0], val = obj[1];
if(typeof(val) != "object") { // Matching name, mobile etc
acc[key] = val;
return acc;
}
if(!Array.isArray(val)) { // Matching meta
for(var k in val)
acc[key + "." + k] = val[k];
return acc;
}
// Matching comments
for(var idx in val) {
for(var k2 in val[idx]) {
acc[key + "." + idx + "." + k2] = val[idx][k2];
}
}
return acc;
};
// var toDotted = R.pipe(R.toPairs, R.reduce(_toDotted, {}));
var toDotted = R.pipe(R.toPairs, R.curry( function(obj) {
return R.reduce(_toDotted, {}, obj);
}));
console.log(toDotted(fieldvalues));
但是,我不确定这是否接近函数式编程方法。它似乎只是围绕着一些功能代码。
任何想法或指针,我可以使用这种更有效的方式编写此代码。
可用的代码段here。
更新1
更新了code以解决问题,旧数据被标记在其中。
由于
答案 0 :(得分:2)
您的解决方案是硬编码的,具有数据结构的固有知识(嵌套的for
循环)。一个更好的解决方案对输入数据一无所知,仍能给你预期的结果。
无论哪种方式,这都是一个非常奇怪的问题,但我特别无聊,所以我想我会试一试。我大多认为这是一个完全没有意义的练习,因为我无法想象预期输出永远比输入更好的情况。
这不是Rambda解决方案,因为它没有理由。您应该将解决方案理解为一个简单的递归过程。如果您能理解它,将其转换为含糖的Rambda解决方案是微不足道的。
// determine if input is object
const isObject = x=> Object(x) === x
// flatten object
const oflatten = (data) => {
let loop = (namespace, acc, data) => {
if (Array.isArray(data))
data.forEach((v,k)=>
loop(namespace.concat([k]), acc, v))
else if (isObject(data))
Object.keys(data).forEach(k=>
loop(namespace.concat([k]), acc, data[k]))
else
Object.assign(acc, {[namespace.join('.')]: data})
return acc
}
return loop([], {}, data)
}
// example data
var fieldvalues = {
name: "hello there",
mobile: "1234",
meta: {status: "new"},
comments: [
{user: "john", comment: "hi"},
{user: "ram", comment: "hello"}
]
}
// show me the money ...
console.log(oflatten(fieldvalues))
总功能
oflatten
相当健壮,可以处理任何输入。即使输入是数组,原始值或undefined
。您可以确定您将始终将对象作为输出。
// array input example
console.log(oflatten(['a', 'b', 'c']))
// {
// "0": "a",
// "1": "b",
// "2": "c"
// }
// primitive value example
console.log(oflatten(5))
// {
// "": 5
// }
// undefined example
console.log(oflatten())
// {
// "": undefined
// }
工作原理......
它需要任何类型的输入,然后......
它使用两个状态变量启动循环:namespace
和acc
。 acc
是您的返回值,并始终使用空对象{}
进行初始化。 namespace
跟踪嵌套键,并始终使用空数组初始化[]
注意我没有使用String来命名密钥,因为任何密钥前面的
''
的根名称空间始终为.somekey
。当您使用[]
的根命名空间时,情况并非如此。使用相同的示例,
[].concat(['somekey']).join('.')
将为您提供正确的密钥'somekey'
。同样,
['meta'].concat(['status']).join('.')
会为您提供'meta.status'
。看到?使用数组进行密钥计算将使这更容易。
循环有第三个参数data
,我们正在处理的当前值。第一次循环迭代将始终是原始输入
我们对data
类型进行了简单的案例分析。这是必要的,因为JavaScript没有模式匹配。仅仅因为使用if/else
并不意味着它不是功能范例。
如果data
是一个数组,我们想迭代数组,并递归调用每个子值的loop
。我们将值的密钥作为namespace.concat([k])
传递,它将成为嵌套调用的新命名空间。请注意,此时没有任何内容被分配给acc
。我们只想在到达acc
时分配给value
,直到那时,我们才建立名称空间。
如果data
是一个Object,我们就像使用Array一样迭代它。对此有一个单独的案例分析,因为对象的循环语法与数组略有不同。否则,它正在做同样的事情。
如果data
既不是数组也不是对象,我们已达到值。此时,我们可以使用构建的data
作为关键字将acc
值分配给namespace
。因为我们已经为这个密钥构建了命名空间,所以我们所要做的就是计算最终密钥namespace.join('.')
并且一切正常。
生成的对象将始终具有与原始对象中找到的值一样多的对。
答案 1 :(得分:2)
功能性方法
只要最终结果(在您的公共API上)是不可变的,您是否将可变对象作为累加器(用于性能)或复制属性(用于纯度)传递并不重要。实际上你已经使用了第三种方法:关联列表(键值对),它将简化Ramda中对象结构的处理。
const primitive = (keys, val) => [R.pair(keys.join("."), val)];
const array = (keys, arr) => R.addIndex(R.chain)((v, i) => dot(R.append(keys, i), v), arr);
const object = (keys, obj) => R.chain(([v, k]) => dot(R.append(keys, k), v), R.toPairs(obj));
const dot = (keys, val) =>
(Object(val) !== val
? primitive
: Array.isArray(val)
? array
: object
)(keys, val);
const toDotted = x => R.fromPairs(dot([], x))
除了连接键并将它们作为参数传递之外,您还可以将R.prepend(key)
映射到每个dot
调用的结果上。