Functional Javascript - 以 FP 方式转换为点分格式(使用 Ramda)
Functional Javascript - Convert to dotted format in FP way (uses Ramda)
我正在 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 以解决旧数据被标记的问题。
谢谢
您的解决方案被硬编码为具有数据结构的固有知识(嵌套的 for
循环)。更好的解决方案应该对输入数据一无所知,但仍会为您提供预期的结果。
无论如何,这是一个非常奇怪的问题,但我特别无聊所以我想我会试一试。我主要发现这是一个完全没有意义的练习,因为我无法想象预期输出 曾经 比输入更好的场景。
This isn't a Rambda solution because there's no reason for it to be. You should understand the solution as a simple recursive procedure. If you can understand it, converting it to a sugary Rambda solution is trivial.
// 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
是您的 return 值,并且始终使用空对象 {}
进行初始化。 namespace
跟踪嵌套键并始终使用空数组初始化,[]
notice I don't use a String to namespace the key because a root namespace of ''
prepended to any key will always be .somekey
. That is not the case when you use a root namespace of []
.
Using the same example, [].concat(['somekey']).join('.')
will give you the proper key, 'somekey'
.
Similarly, ['meta'].concat(['status']).join('.')
will give you 'meta.status'
. See? Using an array for the key computation will make this a lot easier.
循环有第三个参数,data
,我们正在处理的当前值。第一次循环迭代将始终是原始输入
我们对data
的类型做一个简单的案例分析。这是必要的,因为 JavaScript 没有模式匹配。仅仅因为我们使用了 if/else
并不意味着它不是功能范例。
如果 data
是一个数组,我们想要遍历数组,并递归调用每个子值的 loop
。我们将值的键作为 namespace.concat([k])
传递,它将成为嵌套调用的新命名空间。请注意,此时没有任何内容分配给 acc
。我们只想在达到 value
时分配给 acc
,在此之前,我们只是在构建命名空间。
如果 data
是一个对象,我们会像处理数组一样遍历它。对此有一个单独的案例分析,因为对象的循环语法与数组略有不同。否则,它会做同样的事情。
如果 data
既不是数组也不是对象,我们就达到了 值。此时我们可以使用构建的 namespace
作为键将 data
值分配给 acc
。因为我们已经完成了为这个键构建命名空间,所以我们所要做的就是计算最终的键 namespace.join('.')
一切都会成功。
生成的对象将始终具有与在原始对象中找到的值一样多的对。
函数式方法
- 使用递归处理任意形状的数据
- 使用多个微型函数作为构建块
- 对数据使用模式匹配以根据具体情况选择计算方式
无论您是将可变对象作为累加器传递(为了性能)还是复制属性(为了纯度)并不重要,只要最终结果(在您的 public 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
调用的结果。
我正在 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 以解决旧数据被标记的问题。
谢谢
您的解决方案被硬编码为具有数据结构的固有知识(嵌套的 for
循环)。更好的解决方案应该对输入数据一无所知,但仍会为您提供预期的结果。
无论如何,这是一个非常奇怪的问题,但我特别无聊所以我想我会试一试。我主要发现这是一个完全没有意义的练习,因为我无法想象预期输出 曾经 比输入更好的场景。
This isn't a Rambda solution because there's no reason for it to be. You should understand the solution as a simple recursive procedure. If you can understand it, converting it to a sugary Rambda solution is trivial.
// 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
是您的 return 值,并且始终使用空对象{}
进行初始化。namespace
跟踪嵌套键并始终使用空数组初始化,[]
notice I don't use a String to namespace the key because a root namespace of
''
prepended to any key will always be.somekey
. That is not the case when you use a root namespace of[]
.Using the same example,
[].concat(['somekey']).join('.')
will give you the proper key,'somekey'
.Similarly,
['meta'].concat(['status']).join('.')
will give you'meta.status'
. See? Using an array for the key computation will make this a lot easier.循环有第三个参数,
data
,我们正在处理的当前值。第一次循环迭代将始终是原始输入我们对
data
的类型做一个简单的案例分析。这是必要的,因为 JavaScript 没有模式匹配。仅仅因为我们使用了if/else
并不意味着它不是功能范例。如果
data
是一个数组,我们想要遍历数组,并递归调用每个子值的loop
。我们将值的键作为namespace.concat([k])
传递,它将成为嵌套调用的新命名空间。请注意,此时没有任何内容分配给acc
。我们只想在达到value
时分配给acc
,在此之前,我们只是在构建命名空间。如果
data
是一个对象,我们会像处理数组一样遍历它。对此有一个单独的案例分析,因为对象的循环语法与数组略有不同。否则,它会做同样的事情。如果
data
既不是数组也不是对象,我们就达到了 值。此时我们可以使用构建的namespace
作为键将data
值分配给acc
。因为我们已经完成了为这个键构建命名空间,所以我们所要做的就是计算最终的键namespace.join('.')
一切都会成功。生成的对象将始终具有与在原始对象中找到的值一样多的对。
函数式方法
- 使用递归处理任意形状的数据
- 使用多个微型函数作为构建块
- 对数据使用模式匹配以根据具体情况选择计算方式
无论您是将可变对象作为累加器传递(为了性能)还是复制属性(为了纯度)并不重要,只要最终结果(在您的 public 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
调用的结果。