20907

Javascript convert array of objects to tree

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.

Answer1:

A more generic way using lodash and this answer:

<div class="snippet" data-lang="js" data-hide="false" data-console="false" data-babel="false"> <div class="snippet-code">

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>


Answer2:

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

<div class="snippet" data-lang="js" data-hide="false"> <div class="snippet-code">

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


Answer3:

We can do it this with a single for loop:

<div class="snippet" data-lang="js" data-hide="false"> <div class="snippet-code">

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)


Recommend

  • How do you pass in a value to a subfunction in Matlab I am getting output errors?
  • StrRev() Dosent Support UTF-8
  • jQuery filter elements based on data attribute
  • Wordpress - How to change search value using a drop down selector?
  • How to make jQuery `bind` or `on` event handlers idempotent
  • A Read-Only Relational Database on Google App Engine?
  • How to use data from Mongo and PostgreSQL as in-memory lookup tables?
  • How to hide the cursor in windows when dragging and dropping (possibly in python, or another languag
  • How to save R plot image to database?
  • JAXB 2 in an Oracle 10g Webapp
  • angularJS library for drawing bar charts with logarithmic scale
  • how to handle large size of update query in mysql with laravel
  • Calculating gradient norm wrt weights with keras
  • Vectorizing the reshaping and cropping of images using PIL
  • jaxb xsd prefix package names
  • ZF2 Doctrine: When to flush ObjectManager
  • glpk.LPX backward compatiblity?
  • CSS/Javascript to align a word on a specific character
  • How to show a specific VC , more like navigate to a stack of VC
  • mapping between words and a group tuple to get frequency of words
  • Is there an API (SOAP, JSON, XML-RPC, REST, anything) to Google Code Issues?
  • How to get the index of element in the List in c#
  • Implementation of RTTI using typeid
  • Adding independent aspx/asmx pages into DotNetNuke
  • Using an STL Iterator without initialising it
  • Oracle - Second level subquery cannot see field from main query
  • Changing references to deprecated methods C++
  • Configure nginx to return different files to different authenticated users with the same URI
  • how do i write assembly code from c#?
  • Copy to all folders batch file?
  • Django simple Captcha “No module named fields” error
  • D3 nodes and links from JSON with nested arrays of children
  • QLineEdit password safety
  • C# - Serializing and deserializing static member
  • Bug in WPF DataGrid
  • Incrementing object id automatically JS constructor (static method and variable)
  • Validaiting emails with Net.Mail MailAddress
  • Which linear programming package should I use for high numbers of constraints and “warm starts” [clo
  • Javascript + PHP Encryption with pidCrypt
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)