Search code examples
javascriptrecursionlodash

Nested .unionWith, with one unique merge per level


Original data looks like that:

let AddressesBook = [
{
    "userName": "Jay12",
    "doorNumber": "1512",
    "cityID": 19,
    "city": "London",
    "countryID": 1,
    "country": "UK",
    "houseType": "private"
},
{
    "userName": "Jay12",
    "doorNumber": "2003",
    "cityID": 14,
    "city": "York",
    "countryID": 1,
    "universe": "UK",
    "houseType": "private"
},
{
    "userName": "Jay12",
    "doorNumber": "435",
    "cityID": 31,
    "city": "Washington",
    "countryID": 2,
    "universe": "USA",
    "houseType": "private"
},
{
    "userName": "Jay12",
    "doorNumber": "1123",
    "cityID": 18,
    "city": "Oxford",
    "countryID": 1,
    "universe": "UK",
    "houseType": "private"
}


];

i was mapping the data hierarchy by relevant unique ID using Lodash and a suppurated dictionary:

function nestMaker(list, order) {
if (_.isEmpty(order)) return [];
let groups = _.groupBy(list, _.first(order));
return _.map(groups, (children, key) => {
    let group = {};
    group[_.first(order)] = key;
    group.data = nestMaker(children, _.drop(order));
    return _.isEmpty(group.data) ? _.omit(group, 'data') : group;
  });
  }


let hierarchical = nestMaker(AddressesBook, [
"countryID",
"cityID",
"houseType",
"doorNumber"]
 );

it works fine, but i would like to have the name relevant to the id in each level of the object. unfortunately you can't use _.groupBy on two keys. i was thinking about using _.unionWith separately from the first iteration but i couldn't find a way to use it recursively omitting the unnecessary data.

expected output:

 let output =
  [
    {
        "countryID": "1",
        "country": "UK",
        "data": [
            {
                "cityID": "14",
                "city": "York",
                "data": [
                    {
                        "houseType": "private",
                        "data": [
                            {
                                "doorNumber": "2003"
                            }
                        ]
                    }
                ]
            },
            {
                "cityID": "18",
                "city": "Oxford",
                "data": [
                    {
                        "houseType": "private",
                        "data": [
                            {
                                "doorNumber": "1123"
                            }
                        ]
                    }
                ]
            },
            {
                "cityID": "19",
                "city": "London",
                "data": [
                    {
                        "houseType": "private",
                        "data": [
                            {
                                "doorNumber": "1512"
                            }
                        ]
                    }
                ]
            }
        ]
    },
    {
        "countryID": "2",
        "country": "USA",
        "data": [
            {
                "cityID": "31",
                "city": "Washington",
                "data": [
                    {
                        "houseType": "private",
                        "data": [
                            {
                                "doorNumber": "435"
                            }
                        ]
                    }
                ]
            }
        ]
      }
    ];

Solution

  • You can get the 1st item in the group, and extract the name (country, city) from the item:

    const AddressesBook = [{"userName":"Jay12","doorNumber":"1512","cityID":19,"city":"London","countryID":1,"country":"UK","houseType":"private"},{"userName":"Jay12","doorNumber":"2003","cityID":14,"city":"York","countryID":1,"country":"UK","houseType":"private"},{"userName":"Jay12","doorNumber":"435","cityID":31,"city":"Washington","countryID":2,"country":"USA","houseType":"private"},{"userName":"Jay12","doorNumber":"1123","cityID":18,"city":"Oxford","countryID":1,"country":"UK","houseType":"private"}];
    
    const nestMaker = (list, order) => {
      if (_.isEmpty(order)) return [];
      
      const idKey = _.first(order);
      const nameKey = idKey.replace('ID', '');
      let groups = _.groupBy(list, idKey);
      
      return _.map(groups, (children, key) => {
        const group = {};
        const child = _.first(children);
        
        group[idKey] = key;
        if(_.has(child, nameKey)) group[nameKey] = child[nameKey];
        
        group.data = nestMaker(children, _.drop(order));
        return _.isEmpty(group.data) ? _.omit(group, 'data') : group;
      });
    }
    
    
    const hierarchical = nestMaker(AddressesBook, [
      "countryID",
      "cityID",
      "houseType",
      "doorNumber"
    ]);
    
    console.log(hierarchical);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

    If the id and the name keys are doesn't follow the same pattern, you can explicitly state them as entry in the order:

    const AddressesBook = [{"userName":"Jay12","doorNumber":"1512","cityID":19,"city":"London","countryID":1,"universe":"UK","houseType":"private"},{"userName":"Jay12","doorNumber":"2003","cityID":14,"city":"York","countryID":1,"universe":"UK","houseType":"private"},{"userName":"Jay12","doorNumber":"435","cityID":31,"city":"Washington","countryID":2,"universe":"USA","houseType":"private"},{"userName":"Jay12","doorNumber":"1123","cityID":18,"city":"Oxford","countryID":1,"universe":"UK","houseType":"private"}];
    
    const nestMaker = (list, order) => {
      if (_.isEmpty(order)) return [];
      
      const entry = _.first(order);
      const [idKey, nameKey] = Array.isArray(entry) ? entry : [entry];
      let groups = _.groupBy(list, idKey);
      
      return _.map(groups, (children, key) => {
        const group = {}; 
        const child = _.first(children);
        
        group[idKey] = key;
        if(_.has(child, nameKey)) group[nameKey] = child[nameKey];
        
        group.data = nestMaker(children, _.drop(order));
        return _.isEmpty(group.data) ? _.omit(group, 'data') : group;
      });
    }
    
    
    const hierarchical = nestMaker(AddressesBook, [
      ["countryID", "universe"],
      ["cityID", "city"],
      "houseType",
      "doorNumber"
    ]);
    
    console.log(hierarchical);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>