2

I am trying to convert this structure:

var initial = [ 
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];

to this one:

var example = {
"Phase 1": {
    "Step 1": {
        "Task 1": { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, 
        "Task 2": { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }
    } ,
    "Step 2": {
        "Task 1": { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" }, 
        "Task 2": { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }
    }
} ,
"Phase 2": {        
    "Step 1": {
        "Task 1": { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" }, 
        "Task 2": { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
    } ,
    "Step 2": {
        "Task 1": { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" }, 
        "Task 2": { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }     
    }
}
};

so I can easily extract a value like this example['Phase 2']['Step 1']['Task 1']['Value']

I have done the first step using a groupBy function like this one:

function groupBy(d, arr) {
return arr.reduce(function(acc, i) {
    var p = i[d];
    var temp = acc[p] || [];
    temp.push(i);
    acc[p] = temp;
    return acc;
}, {})
}

so when I do var groupedByPhase = groupBy('Phase', initial); I get for groupedByPhase:

{
"Phase 1" : [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
],
"Phase 2": [
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
] 
}

I also manage to group by Step using this function:

function groupByInNestedObj(item, obj) {
var x = {};
for (var i in obj) {
    x[i] = groupBy(item, obj[i]);
}
return x;
}

which enables to get when calling groupByInNestedObj('Step', groupBy('Phase', initial))

 {
"Phase 1" : {
    "Step 1": [
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }],
    "Step 2": [
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
    ]},
"Phase 2": {
    "Step 1:" [
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }],
    "Step 2:"[
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
    ]}
}

However I am a bit stuck here, to do the next one. Ideally I would like to be able to do groupBy("Task", groupBy("Step", groupBy("Phase", initial))) so that groupBy groups at the deepest level of the tree. Any suggestion welcome!

Note: I did try for the 2nd step to implement this function

function groupByInNestedObj2 (item, obj) {
    var x = {};
    for(var i in obj) {
        for (var j in obj[i]) {
            x[i][j] = groupBy(item, obj[i][j]);
        }
    }
    return x;
}

but it doesn't seem to work.

Note 2: The second version of the previous function works but is not pure as it modifies the object passed through it

function groupByInNestedObj2 (item, obj) {
    var x = {};
    for(var i in obj) {
        x[i] = obj[i]
        for (var j in x[i]) {
            x[i][j] = groupBy('Task', x[i][j]);
        }
    }
    return x;
}

so when I do var groupByPhaseAndStepAndTask = groupByInNestedObj2('Task', groupByPhaseAndStep) groupByPhaseAndStep is modified too, which is an undesirable side effect. Still working on it.

3 Answers 3

1

You can try use .reduce and just check if object exists or not, like this

var initial = [ 
  { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
  { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
  { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
  { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
  { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
  { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
  { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
  { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];

var result = initial.reduce(function (prev, current) {
  prev[current.Phase] = prev[current.Phase] || {};
  prev[current.Phase][current.Step] = prev[current.Phase][current.Step] || {};
  prev[current.Phase][current.Step][current.Task] = current;
  return prev;
}, {});

console.log(JSON.stringify(result, null, 2));

Sign up to request clarification or add additional context in comments.

Comments

1

We can do it this with a single for loop:

var initial = [ 
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
var final = {}
initial.forEach(function(d){
  if (!final[d.Phase]) //phase not present so make an object
  	final[d.Phase] = {};
  if (!final[d.Phase][d.Step]) //step not present so make an object
  	final[d.Phase][d.Step] = {};
  if (!final[d.Phase][d.Step][d.Task])//task not present so make an object and store the object
  	final[d.Phase][d.Step][d.Task] = d;
  
})
console.log(final)

1 Comment

While it does give the right result, what I am trying to achieve, on top of the result, is composition, see my groupBy("Task", groupBy("Step", groupBy("Phase", initial))) comment
0

A more generic way using lodash and this answer:

var initial = [
  { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
  { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
  { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
  { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
  { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
  { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
  { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
  { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];

_.mixin({
  groupByMulti: function(obj, values, context) {
    if (!values.length) return obj;
    var byFirst = _.groupBy(obj, _.head(values), context);
    for (var prop in byFirst) {
      byFirst[prop] = _.groupByMulti(byFirst[prop], _.tail(values), context);
    }
    return byFirst;
  }
});

var tree = _(initial).groupByMulti(['Phase', 'Step', 'Task']);
$('#pre').append(JSON.stringify(tree, null, 3));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.0/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre id='pre'></pre>

2 Comments

It's pretty close to the desired result, inside the tasks are object not arrays, but that would be ok. Although I use lodash for some things, I am not too familiar with _.mixin so there is almost too much magic in your answer. Additionnally, I aim to achieve composition of function so I can do something like groupBy("Task", groupBy("Step", groupBy("Phase", initial)))
Ok I managed to do it by refactoring your example, using simple head, tail and groupBy functions, I like the use of recursion, now just need to amend it so when array.length == 1, i get the object and not an array with a single element

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.