How to refactor/fix algorithim from mutating a reference to using inline variable

Viewed 18

The function takes an input path like a.b.c and should output a nested structure json like:

{
   a: {
      b: {
         c: {}
      }
   }
}

The algorithm using iterative style is:

function stringToObj(path, obj) {
  var parts = path.split(".");
  var part;
  while ((part = parts.shift())) {
    if (typeof obj[part] != "object") obj[part] = {};
    obj = obj[part]; // line 6
  }
}

Current usage:

let result = {};
stringToObj("a.b.c", result);
console.log(result); // outputs the json

JsFiddle

The problem:

It relies mutating the obj parameter on line 6. I would like to not rely upon passing the result object, and rather create one inside the function. Doing so results in different results. A desired example usage:

const result = stringToObj("a.b.c"); // result should be the json

Context:

The exercise is for learning purpose. Main objective is understanding why removing obj and rewriting the function as per follows doesn't work as expected:

function stringToObj(path) {
  var obj = {};
  var parts = path.split(".");
  var part;
  while ((part = parts.shift())) {
    if (typeof obj[part] != "object") obj[part] = {};
    obj = obj[part]; // line 6
  }
  return obj;
}
1 Answers

After splitting by .s, you can use reduceRight to start at the last property, c, while taking an initial value of an empty object. Inside the callback, use a computed property to return a new object containing the old object at the property being iterated over:

const stringToObj = str => str.split('.').reduceRight(
  (lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));

If you're not familiar with it, reduceRight is like reduce, except that it iterates starting from the last element in the array and going backwards, instead of starting from the first element of the array and going forwards. On each iteration, the callback is called, where the first argument (here, lastObj) is the value returned from the last iteration, and the second argument is the current item of the array being iterated over.

You can also reverse the array of properties and use reduce instead of reduceRight, which might be easier to understand at a glance, but it's a bit less elegant:

const stringToObj = str => str.split('.').reverse().reduce(
  (lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));

Also, don't mix var and let. If you're going to use ES2015 syntax - which you should - consider always using const, and only use let when you must reassign. Never use var, it has too many gotchas to be worth using in modern code.

Related