Search code examples
javascriptd3.jslodash

Sort names in subgroups into groups - Restructuring json data


I'm trying to restructure my JSON data to pass it to D3.js to create a tree diagram. The link below has dummy data. The origin data includes different fruit / vegetable names with some properties like groups of this fruit (e.g. {name: apple, group1: tree fruit}) but I want to structure the fruit names into the subgroups and the subgroups into the top level groups (e.g. fruits > tree fruits > apple). You will find the correct structure I try to get on the last lines as comment.

My problem at the moment is that the group names repeats many times and I cant find a way to get the group3 names (and probably 4 and 5) once to push it to the new data structure. You will find the logical error on line 145 but I will copy my try to get the group3 names in this post, too.

The project: https://codepen.io/phng/pen/JpGEVg?editors=0010

if (group3) {
  var checkGroup3 = function(recentName) {
    for (var j = 0; j < materialData.children.length; j++) {
      for (var k = 0; k < materialData.children[j].children.length; k++) {
        console.log(_.find(materialData.children[j].children[k].children, {
          'name': recentName
        }));
        if (!_.find(materialData.children[j].children[k].children, {
            'name': recentName
          })) {
          //console.log(recentName);
        }
      }
    }
  }
  checkGroup3(); // this function was a try to get the group3 names once, failed
  if (i != 0) {
    if (group.name != data.children[i - 1].gruppe_3) { // error here. Condition checks the group 3 aka "gruppe_3" multiple times because in first round the group name isn't in the JSON, then another group name follows, is also not in JSON but then the first one comes again and the group name isn't again like the grupp_3 name and get pushed to the JSON again.
      materialData.children[aboveGroupPosition].children[aboveGroupPositionThirdLayer].children.push(group)
    }
  } else if (i === 0) {
    materialData.children[aboveGroupPosition].children[aboveGroupPositionThirdLayer]

      .children.push(group)
  }
}

origin data:

var data = {
'name': 'Big Data',
'children': [
    {
        'name': 'Apple',
        'gruppe_1': 'Fruits',
        'gruppe_2': 'Red Fruits',
        'gruppe_3': 'Tree Fruits',
        'gruppe_4': 'Winter Favourite Fruits',
        'gruppe_5': 'Candy Fruits'
    },
    {
        'name': 'Pomegranate',
        'gruppe_1': 'Fruits',
        'gruppe_2': 'Red Fruits',
        'gruppe_3': 'Tree Fruits',
        'gruppe_4': 'Winter Favourite Fruits',
        'gruppe_5': 'Candy Fruits'
    },
    {
        'name': 'Pear',
        'gruppe_1': 'Fruits',
        'gruppe_2': 'Green Fruits',
        'gruppe_3': 'Medium Tree Fruits'
    },
    {
        'name': 'Strawberry',
        'gruppe_1': 'Nuts',
        'gruppe_2': 'Red Nuts',
        'gruppe_3': 'Awkward Nuts'
    },
    {
        'name': 'Hazelnuts',
        'gruppe_1': 'Nuts',
        'gruppe_2': 'Brown Nuts',
        'gruppe_3': 'Normal Nuts'
    },
    {
        'name': 'Cucumber',
        'gruppe_1': 'Vegetable',
        'gruppe_2': 'Green Vegetable',
        'gruppe_3': 'Long Vegetable'
    },
    {
        'name': 'Maracuya'
    },
    {
        'name': 'Green Kiwi',
        'gruppe_1': 'Fruits',
        'gruppe_2': 'Green Fruits'
    },
    {
        'name': 'Cherry',
        'gruppe_1': 'Fruits',
        'gruppe_2': 'Red Fruits',
        'gruppe_3': 'Tree Fruits'
    },
]

}

Structure I try to get:

{
  "name":"Big Data",
  "children":[
    {
      "name":"Fruits",
      "children":[
        "Maracuya",
        {
          "name":"Red Fruits",
          "parent":"Fruits",
          "children":[
            {
              "name":"Tree Fruits",
              "children":[
                "Cherry",
                {
                  "name":"Winter Favourite Fruits",
                  "children":[
                    {
                      "name":"Candy Fruits",
                      "children":[
                        "Apple",
                        "Pomegranate"
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        },
        {
          "name":"Green Fruits",
          "children":[
            "Green Kiwi",
            {
              "name":"Medium Tree Fruits",
              "children":[
                "Pear"
              ]
            }
          ]
        }
      ]
    },
    {
      "name":"Vegetable",
      "children":[
        {
          "name":"Green Vegetable",
          "children":[
            {
              "name":"Long Vegetable",
              "children":[
                "Cucumber"
              ]
            }
          ]
        }
      ]
    },
    {
      "name":"Nuts",
      "children":[
        {
          "name":"Red Nuts",
          "children":[
            {
              "name":"Awkward Nuts",
              "children":[
                "Strawberry"
              ]
            }
          ]
        },
        {
          "name":"Brown Nuts",
          "children":[
            {
              "name":"Normal Nuts",
              "children":[
                "Hazelnuts"
              ]
            }
          ]
        }
      ]
    }
  ]
}

How I tried to restructure the data:

var materialData = {
  'name': 'Big Data',
  'children': []
};


var numberOfElements = data.children.length;

for (var i = 0; i < numberOfElements; i++) {
  var elemObject = data.children.slice(i, i + 1),
    elemInArray = elemObject[0],
    elem = {},
    elem = elemInArray;
  var name = elem.name,
    preview = elem.preview,
    group1 = elem.gruppe_1,
    group2 = elem.gruppe_2,
    group3 = elem.gruppe_3,
    group4 = elem.gruppe_4,
    group5 = elem.gruppe_5;


  // group 1
  var group = {
    'name': group1,
    // 'parent': 'Material Archiv',
    'children': []
  }
  // get first layer groups
  if (i != 0) {
    if (group.name != data.children[i - 1].gruppe_1) {
      materialData.children.push(group)
    }
  } else if (i === 0) {
    materialData.children.push(group)
  }

  // group 2
  var group = {
    'name': group2,
    // 'parent': 'Material Archiv',
    'children': []
  }

  var aboveGroupPosition = materialData.children.map(function(g) {
    return g.name;
  }).indexOf(group1);

  // get second layer groups
  if (group2) {
    if (i != 0) {
      if (group.name != data.children[i - 1].gruppe_2) {
        materialData.children[aboveGroupPosition].children.push(group)
      }
    } else if (i === 0) {
      materialData.children[aboveGroupPosition].children.push(group)
    }
  }

  // group 3
  var group = {
    'name': group3,
    // 'parent': '',
    'children': []
  }

  var aboveGroupPositionThirdLayer = materialData.children[aboveGroupPosition].children.map(function(g) {
    return g.name;
  }).indexOf(group2);

  // get third layer groups
  if (group3) {
    var checkGroup3 = function(recentName) {
      for (var j = 0; j < materialData.children.length; j++) {
        for (var k = 0; k < materialData.children[j].children.length; k++) {
          console.log(_.find(materialData.children[j].children[k].children, {
            'name': recentName
          }));
          if (!_.find(materialData.children[j].children[k].children, {
              'name': recentName
            })) {
            //console.log(recentName);
          }
        }
      }
    }
    checkGroup3(); // this function was a try to get the group3 names once, failed
    if (i != 0) {
      if (group.name != data.children[i - 1].gruppe_3) { // error here. Condition checks the group 3 aka "gruppe_3" multiple times because in first round the group name isnt in the json, then another group name follows, is also not in json but then the first one comes again and the group name isnt again like the grupp_3 name and get pushed to the json again.
        materialData.children[aboveGroupPosition].children[aboveGroupPositionThirdLayer].children.push(group)
      }
    } else if (i === 0) {
      materialData.children[aboveGroupPosition].children[aboveGroupPositionThirdLayer].children.push(group)
    }
  }

}

console.log(materialData);

Solution

  • An approach.

    You could take an iterative approach by iterating the keys for the nested groups and search for a group. If not found, generate a new group with children and go on until all groups are in the object.

    At last assign the name property to a new group, in front of a possible following object.

    var data = { name: "Big Data", children: [{ name: "Apple", gruppe_1: "Fruits", gruppe_2: "Red Fruits", gruppe_3: "Tree Fruits", gruppe_4: "Winter Favourite Fruits", gruppe_5: "Candy Fruits" }, { name: "Pomegranate", gruppe_1: "Fruits", gruppe_2: "Red Fruits", gruppe_3: "Tree Fruits", gruppe_4: "Winter Favourite Fruits", gruppe_5: "Candy Fruits" }, { name: "Pear", gruppe_1: "Fruits", gruppe_2: "Green Fruits", gruppe_3: "Medium Tree Fruits" }, { name: "Strawberry", gruppe_1: "Nuts", gruppe_2: "Red Nuts", gruppe_3: "Awkward Nuts" }, { name: "Hazelnuts", gruppe_1: "Nuts", gruppe_2: "Brown Nuts", gruppe_3: "Normal Nuts" }, { name: "Cucumber", gruppe_1: "Vegetable", gruppe_2: "Green Vegetable", gruppe_3: "Long Vegetable" }, { name: "Maracuya" }, { name: "Green Kiwi", gruppe_1: "Fruits", gruppe_2: "Green Fruits" }, { name: "Cherry", gruppe_1: "Fruits", gruppe_2: "Red Fruits", gruppe_3: "Tree Fruits" }] },
        result = { name: data.name, children: [] };
    
    data.children.forEach(function (object) {
        const getKey = i => 'gruppe_' + i;
    
        var i = 1, index,
            level = result.children,
            temp,
            key = getKey(i);
    
        while (key in object) {
            temp = level.find(({ name }) => object[key] === name);
            if (!temp) {
                temp = { name: object[key], children: [] };
                level.push(temp);
            }
            level = temp.children;
            key = getKey(++i);
        }
        index = level.findIndex(o => typeof o === 'object');
        level.splice(index === -1 ? level.length : index, 0, object.name);
    });
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }