-1

My task is straight forward. I have an array of strings:

let a=["a","b","c"];

And i want to convert that array to (can alter the original array, doesn't matter) what i would like to call as "recursive object" just so:

//in json format just to demonstrate
"object": {
    "a": {
      "b":{
        "c":{
          
        }
        
      }
    }
  }

I've tried the following logic but couldn't get it to work due to reference problem and i couldn't build recursion.

let tempObj;
let obj2;

array.slice().reverse().forEach(function(item,index){
    
        obj2=tempObj;
        tempObj[item]="";
      
    });

Just to make sure we are on the same page, another example:

let arr=["alpha","beta","gamma"];
let magicObj=someMagicFunction(arr);
//magicObj["alpha"]["beta"]["gamma"] contains the value ""

Thanks

Peter Seliger
  • 4,001
  • 1
  • 20
  • 27
Skywarth
  • 180
  • 3
  • 15
  • It is not clear. In the first example there is no empty string anywhere, yet your code assigns the empty string. In your second example, you say the deepest property is an empty string. So which is it? An empty string, or an empty object? – trincot Feb 04 '21 at 21:02

5 Answers5

2

Start aggregating your object from the array's right most side via reduceRight and provide e.g. an empty object / {} or an empty string / "" as this method's initial value(s) ...

console.log(
  'object :',
  ["a","b","c"]
    .reduceRight((obj, key) =>
      ({ [key]: obj }), {}
    )
);
console.log(
  'object :',
  ["alpha","beta","gamma"]
    .reduceRight((obj, key) =>
      ({ [key]: obj }), ""
    )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

... and since code code-reuse always should be a goal the above examples change to ...

function createObjectWithParentKeyAndChildValue(value, key) {
  return { [key]: value };
}
console.log(
  'object :',
  ['a', 'b', 'c']
    .reduceRight(createObjectWithParentKeyAndChildValue, {})
);
console.log(
  'object :',
  ['alpha', 'beta', 'gamma']
    .reduceRight(createObjectWithParentKeyAndChildValue, '')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 4,001
  • 1
  • 20
  • 27
  • Rather than reuse by extracting the `reduce` callback, why not turn this into a function taking the path and internal value and returning the nested object? – Scott Sauyet Feb 04 '21 at 21:30
  • 1
    For array methods the reuse already starts at the level of the higher order functions. These callback functions are tools, tailored/customized for specific tasks. They belong into a programmers toolbox/tool-belt. And if named correctly, the combination of an array's method-name and its named callback makes such code fun to read. – Peter Seliger Feb 04 '21 at 21:44
  • 1
    That's understood, and if I were to write this in Ramda, it would just be `const buildDeep = reduceRight (objOf)`, since `objOf` matches your `createObjectWithParentKeyAndChildValue`. We would use it like `buildDeep ({}, ['a', 'b', 'c'])`. But that `buildDeep` is to me the real value here, and not a number of expressions that call `createObjectWithImGettingTiredOfTypingThisNameParentKeyAndChildValue`. Maybe that's just the functional programmer genes in me coming out. – Scott Sauyet Feb 04 '21 at 21:54
  • @ScottSauyet ... I got raised into a programmers life with math and a lot of recursion. But I also got influenced by Ruby and this JavaGuysWhichWouldExplainToMeWhyLongSpeakingMethodsAreImportant. I went with/through too many schools; I'm a lost case. – Peter Seliger Feb 04 '21 at 22:00
  • Whereas I'm probably a hopeless cause in the other direction, a big fan of John DeGoes' [Descriptive Variable Names: A Code Smell](https://degoes.net/articles/insufficiently-polymorphic). :-) – Scott Sauyet Feb 04 '21 at 22:14
1

Here is a simple solution:

const arr = ["a","b","c"];
const result = arr.reverse().reduce((obj, key) => ({[key]: obj}), {})
console.log(result)

Here a little explaination:
o is the result of the last iteration and v is the current element in the array. {[v]: o} creates a new object and sets the property v to o and returns that.

Wendelin
  • 2,069
  • 1
  • 7
  • 21
  • Love it, simple and working good. Just one thing, out of topic: when i run the same code on JS fiddle, it prints out (console.log) { a: { b: { ... } } } Therefore missing the c, any idea why it could be the case ? – Skywarth Feb 04 '21 at 20:41
  • 1
    Because JSFiddle does not show the whole result, thats why there are three dots (...) – Wendelin Feb 04 '21 at 20:42
  • oh nevermind, JS fiddle just hides the depth after some point. Thanks – Skywarth Feb 04 '21 at 20:43
  • 2
    Why `reverse` and `reduce` rather than a single `reduceRight`? – Scott Sauyet Feb 04 '21 at 20:57
  • `reduceRight` is IMO a better solution but I did not want to 'steal' from Peter Seliger. Should I change my answer regadless? – Wendelin Feb 04 '21 at 21:02
  • Not at all. The more the merrier. I just didn't know if there was a strong reason. – Scott Sauyet Feb 04 '21 at 21:06
1
let magicObj = arr.reverse().reduce((obj, prop) => ({ [prop]: obj }), {})
DDomen
  • 887
  • 1
  • 3
  • 13
1

there is my pure recursive answer:

let a=["a","b","c"];

const b = (arr = [], obj = null) => {
  if (arr.length > 0) {    
    const { length, [length - 1]: last, ...r } = arr;
    const rest = Object.values(r);
    const nextObj = obj ? { [last]: obj } : { [last]: {} };
    return b(rest, nextObj);
  }
  return obj;
};

console.log(b(a));
Peter
  • 1,019
  • 5
  • 12
  • 1
    I think my recursive solution is simpler. But I am definitely going to steal that `{ length, [length - 1]: last, ...r } = arr` trick. That's very clever. – Scott Sauyet Feb 04 '21 at 21:14
1

The reduce / reduceRight answers are great. But this can also be done with a fairly trivial recursive version:

const buildDeep = ([p, ...ps], val) =>
  p == undefined ? val : {[p]: buildDeep (ps, val)}

console .log (buildDeep (['a', 'b', 'c'], {}))
console .log (buildDeep (['alpha', 'beta', 'gamma'], ''))

To my mind, this is even simpler than reduce. It feels related to the various path-setting functions you see around, but is less complex since it doesn't have to work with an existing object.

Scott Sauyet
  • 37,179
  • 4
  • 36
  • 82