rsmoorthy rsmoorthy - 4 months ago 10
Javascript Question

Functional Javascript - Convert to dotted format in FP way (uses Ramda)

I am learning functional programming in Javascript and using Ramda. I have this object

var fieldvalues = { name: "hello there", mobile: "1234",
meta: {status: "new"},
comments: [ {user: "john", comment: "hi"},
{user:"ram", comment: "hello"}]
};


to be converted like this:

{
comments.0.comment: "hi",
comments.0.user: "john",
comments.1.comment: "hello",
comments.1.user: "ram",
meta.status: "new",
mobile: "1234",
name: "hello there"
}


I have tried this Ramda source, which works.

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));


However, I am not sure if this is close to Functional programming methods. It just seems to be wrapped around some functional code.

Any ideas or pointers, where I can make this more functional way of writing this code.

The code snippet available here.

UPDATE 1

Updated the code to solve a problem, where the old data was getting tagged along.

Thanks

Answer

A functional approach would

  • use recursion to deal with arbitrarily shaped data
  • use multiple tiny functions as building blocks
  • use pattern matching on the data to choose the computation on a case-by-case basis

Whether you pass through a mutable object as an accumulator (for performance) or copy properties around (for purity) doesn't really matter, as long as the end result (on your public API) is immutable. Actually there's a nice third way that you already used: association lists (key-value pairs), which will simplify dealing with the object structure in 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))

Alternatively to concatenating the keys and passing them as arguments, you can also map R.prepend(key) over the result of each dot call.