11

I have the following array:

var sampleArray = [
  "CONTAINER",
  "BODY",
  "NEWS",
  "TITLE"];

I want to have the following output:

var desiredOutput = [{
        "CONTAINER": [{
            "BODY": [{
                "NEWS": [{
                    "TITLE": []
                }]
            }]
        }]
    }];

How can I achieve this in JavaScript?

Already tried with recursive loop, but it does not work, gives me undefined.

    dataChange(sampleArray);
    function dataChange(data) {
        for (var i = 0; i < data.length; i++) {
            changeTheArray[data[i]] = data[i + 1];
            data.splice(i, 1);
            dataChange(changeTheArray[data[i]]);
        }
    }

Thanks

andika23
  • 123
  • 7

3 Answers3

9

This does what you're asking for, in one line, and with no additional variables:

let desiredOutput = sampleArray.reduceRight((obj, key) => [ { [key]: obj } ], []);

The reduceRight call, starting from the right hand end of the array, progressively accumulates the current data (seeded with the initial value of []) as the value of the single key in a new object { [key] : _value_ } where that object is itself the single entry in an array [ ... ].

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • 2
    Nice witchcraft... I tried to build a `reduce` method, but couldn't get it to work due to the accumulator moving down the tree... The `reverse` is a clever touch :D – Cerbrus Oct 26 '17 at 09:27
  • @Xufox yeah, I was just reaching that conclusion myself while you were commenting - my original tests got confused by the fact that `.reverse()` does an _in place_ reversal and my test data had already been corrupted. So using `.reduceRight` is actually better since it leaves the original array untouched. – Alnitak Oct 26 '17 at 09:27
  • @Cerbrus actually, `reduceRight()` is better. – Alnitak Oct 26 '17 at 09:29
  • @Cerbrus I also tried it with `reduce` and building on a value similar to `{data: [], referenceToInnerArray: []}`, but Alnitak got it done sooner… – Sebastian Simon Oct 26 '17 at 09:29
  • Even better :D I had no idea there was such a thing as `reduceRight`... – Cerbrus Oct 26 '17 at 09:31
  • @Cerbrus I had forgotten about it myself for a while - it's used far less frequently than `.reduce()`. – Alnitak Oct 26 '17 at 09:32
3

This will do it:

const sampleArray = ["CONTAINER", "BODY", "NEWS", "TITLE"];
const data = [];    // Starting element.
let current = data; // Pointer to the current element in the loop

sampleArray.forEach(key => {     // For every entry, named `key` in `sampleArray`,
    const next = [];             // New array
    current.push({[key]: next}); // Add `{key: []}` to the current array,
    current = next;              // Move the pointer to the array we just added.
});

console.log(data);

{[key]: next} is relatively new syntax. They're computed property names.

This:

const a = 'foo';
const b = {[a]: 'bar'};

Is similar to:

const a = 'foo';
const b = {};
b[a] = 'bar';

You could re-write the forEach as a one-liner:

const sampleArray = ["CONTAINER", "BODY", "NEWS", "TITLE"];
const data = [];    // Starting element.
let current = data; // Pointer to the current element in the loop

sampleArray.forEach(key => current.push({[key]: current = [] }));

console.log(data);

This current.push works a little counter-intuitively:

  1. Construct a new element to push. This assigns a new value to current.
  2. Push the new element to the reference .push was called on.
    • That reference is the value of current before current = [].
Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • 1
    question askers are fickle beasts ;-) – Alnitak Oct 26 '17 at 09:46
  • I'd go for an additional temporary variable to avoid the dereference in your function's second line: `let next = []; current.push({[key] : next}); current = next` – Alnitak Oct 26 '17 at 09:47
  • 1
    @Alnitak: Yea, I was a little surprised. Good suggestion :-) – Cerbrus Oct 26 '17 at 10:02
  • Re: your edit - why abuse `.forEach` with poorly understood ordering of the use of that `current` variable when `.reduce` is specifically designed to handle that accumulation? I had considered a similar solution but without reference to the language spec I couldn't be sure whether the assignment to `current = []` happens before or after the `.push`. – Alnitak Oct 26 '17 at 12:28
  • @Alnitak: The moment you call `.push`. the `this` variable in `.push` is set to the old value of `current`. That reference doesn't change. I'm not claiming it's a good piece of code. I just realized it was possible, and wanted to add it. – Cerbrus Oct 26 '17 at 13:03
  • How can you be sure? How do you know that the parameters aren't evaluated *before* the variable is used as the function's `this` object? You may well be right, but code like that is very hard to reason about, and IMHO best avoided. In other languages it definitely (well, probably...) _wouldn't_ work the way you describe. – Alnitak Oct 26 '17 at 13:14
  • It looks like you are right - see http://es5.github.io/#x11.2.3 but my comments above still stand. – Alnitak Oct 26 '17 at 13:22
  • Again, _"I'm not claiming it's a good piece of code."_ It's just interesting, and something the reader can maybe learn something from. – Cerbrus Oct 26 '17 at 13:45
1

Hi i made a little demo :

var sampleArray = [
      "CONTAINER",
      "BODY",
      "NEWS",
      "TITLE"
    ], 
    generateArray = [], 
    tmp = null;

for(var i = 0; i < sampleArray.length; i++) {
  if(tmp===null){
    generateArray[sampleArray[i]] = {};
    tmp = generateArray[sampleArray[i]];
  }else{
    tmp[sampleArray[i]] = {};
    tmp = tmp[sampleArray[i]];
  }         
}

console.log(generateArray);
Cerbrus
  • 70,800
  • 18
  • 132
  • 147
Álvaro Touzón
  • 1,247
  • 1
  • 8
  • 21