功能性Javascript - 以FP方式转换为点线格式(使用Ramda)

时间:2016-07-30 15:18:57

标签: javascript functional-programming ramda.js

我正在学习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以解决问题,旧数据被标记在其中。

由于

2 个答案:

答案 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
// }

工作原理......

  1. 它需要任何类型的输入,然后......

  2. 它使用两个状态变量启动循环:namespaceaccacc是您的返回值,并始终使用空对象{}进行初始化。 namespace跟踪嵌套键,并始终使用空数组初始化[]

      

    注意我没有使用String来命名密钥,因为任何密钥前面的''的根名称空间始终为.somekey。当您使用[]的根命名空间时,情况并非如此。

         

    使用相同的示例,[].concat(['somekey']).join('.')将为您提供正确的密钥'somekey'

         

    同样,['meta'].concat(['status']).join('.')会为您提供'meta.status'。看到?使用数组进行密钥计算将使这更容易。

  3. 循环有第三个参数data,我们正在处理的当前值。第一次循环迭代将始终是原始输入

  4. 我们对data类型进行了简单的案例分析。这是必要的,因为JavaScript没有模式匹配。仅仅因为使用if/else并不意味着它不是功能范例。

  5. 如果data是一个数组,我们想迭代数组,并递归调用每个子值的loop。我们将值的密钥作为namespace.concat([k])传递,它将成为嵌套调用的新命名空间。请注意,此时没有任何内容被分配给acc。我们只想在到达acc时分配给value,直到那时,我们才建立名称空间。

  6. 如果data是一个Object,我们就像使用Array一样迭代它。对此有一个单独的案例分析,因为对象的循环语法与数组略有不同。否则,它正在做同样的事情。

  7. 如果data既不是数组也不是对象,我们已达到。此时,我们可以使用构建的data作为关键字将acc值分配给namespace。因为我们已经为这个密钥构建了命名空间,所以我们所要做的就是计算最终密钥namespace.join('.')并且一切正常。

  8. 生成的对象将始终具有与原始对象中找到的值一样多的对。

答案 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调用的结果上。