Search code examples
javascriptsqlarraysloopbackflatten

Loopback Response to Tabular multi Array / Flattening nested Arrays of objects to linear Array


I want to convert Loopback response into tabularize/flattened multiple arrays (for e.g. the response from adjacent sql query rather than loopback). Sample Input:

let data = [{
  id: 1,
  type: "sedan",
  vehicles: [{
      id: 1,
      name: 'audi',
      tyres: [{
          id: 1,
          size: "40inch",
          manufacturer: [{
              id: 1,
              name: "General",
              branches: [{
                  id: "1",
                  city: "munich"
                },
                {
                  id: "2",
                  city: "cologne"
                }
              ]
            },
            {
              id: 2,
              name: "Rosana",
              branches: [{
                  id: "3",
                  city: "paris"
                },
                {
                  id: "4",
                  city: "venice"
                }
              ]
            }
          ]
        },
        {
          id: 2,
          size: "60inch",
          manufacturer: [{
            id: 1,
            name: "General",
            branches: [{
                id: "1",
                city: "munich"
              },
              {
                id: "2",
                city: "cologne"
              }
            ]
          }]
        }
      ]
    },
    {
      id: 2,
      name: 'mercedes',
      tyres: [{
        id: 2
        size: "60inch",
        manufacturer: [{
          id: 1,
          name: "General",
          branches: [{
              id: "1",
              city: "munich"
            },
            {
              id: "2",
              city: "cologne"
            }
          ]
        }]
      }]
    }
  ]
}]

Expected Output:

expectedOutput = [{
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 1,
    "vehicles.name": "audi",
    "tyres.id": 1,
    "tyres.size": "40inch",
    "manufacturer.id": 1,
    "manufacturer.name": "General",
    "branches.id": 1,
    "branches.city": "munich"
  },
  {
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 1,
    "vehicles.name": "audi",
    "tyres.id": 1,
    "tyres.size": "40inch",
    "manufacturer.id": 1,
    "manufacturer.name": "General",
    "branches.id": 2,
    "branches.city": "cologne"
  },
  {
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 1,
    "vehicles.name": "audi",
    "tyres.id": 1,
    "tyres.size": "40inch",
    "manufacturer.id": 2,
    "manufacturer.name": "Rosana",
    "branches.id": 3,
    "branches.city": "paris"
  },
  {
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 1,
    "vehicles.name": "audi",
    "tyres.id": 1,
    "tyres.size": "40inch",
    "manufacturer.id": 2,
    "manufacturer.name": "Rosana",
    "branches.id": 4,
    "branches.city": "venice"
  },
  {
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 2,
    "vehicles.name": "mercedes",
    "tyres.id": 1,
    "tyres.size": "60inch",
    "manufacturer.id": 1,
    "manufacturer.name": "General",
    "branches.id": 1,
    "branches.city": "munich"
  },
  {
    "id": 1,
    "type": "sedan",
    "vehicles.id.": 2,
    "vehicles.name": "mercedes",
    "tyres.id": 2,
    "tyres.size": "60inch",
    "manufacturer.id": 1,
    "manufacturer.name": "General",
    "branches.id": 2,
    "branches.city": "cologne"
  }
]

My code is as follows, but somehow its overwriting the data once recursion is called or when the flat function is called more than once (without recursion). Flat function attempts to flatten 1 row/level at each call. Maybe there is a better recursive approach which i am failed to reach out.

function flat(arr, result) {
  arr.forEach(row => {
    let rows = [];
    keys = Object.keys(row);
    keys.forEach(key => {
      if (Array.isArray(row[key])) {
        rows.push(...merge(rows, row[key], key));
      } else {
        rows.push(row);
      }
    })

    result.push(...rows);
  })
  console.log(result);
// TODO 
   // flat(result, []);
  // each call linerize each level, 
  //should be called untill the whole array is linearized
   //

  return result;
}

function merge(rows, arr, alias) {
  if (!rows.length) {
    rows = [{}]
  }
  let a = arr.map(row => {
    keys = Object.keys(row);
    keys.forEach(key => {
      rows.forEach(r => {
        if (Array.isArray(row[key])) {
          r[key] = row[key];
        } else {
          r[alias + '.' + key] = row[key];
        }
        delete r[alias];
      })
    })
  })

  return a.filter(x=>{
    if(x!== null){
      return x;
    }
  });

Solution

  • I have rewritten the function that flattens the data. Call it with the data object and an empty string as keyPrefix. It keeps the complete prefix instead of just the last parent (fixing it should be straight forward).

    function deepCopy(obj) {
        return JSON.parse(JSON.stringify(obj)); // Can be replaced by a better way to deep copy
    }
    
    function isPrimitive(test) {
        return (test !== Object(test));
    }
    
    function compress(data, keyPrefix) {
        let response = {};
    
        if (isPrimitive(data)) {
            response[keyPrefix] = data;
        }
    
        else if (Array.isArray(data)) {
            response = [];
            data.forEach(entry => {
                let compressed = compress(entry, keyPrefix);
    
                let item = {};
    
                if (Array.isArray(compressed)) {
                    response.push.apply(response, compressed);
                }
    
                else {
                    Object.keys(compressed).forEach(key => {
                        item[key] = compressed[key];
                    });
    
                    response.push(item);
                }
            });
        }
    
        else {
            Object.keys(data).forEach(key => {
                let entry = data[key];
    
                let compressed = compress(entry, keyPrefix ? keyPrefix + "." + key : key);
    
                if (Array.isArray(compressed)) {
                    if (!Array.isArray(response)) {
                        response = [response];
                    }
    
                    let newItems = [];
    
                    response.forEach(already => {
                        compressed.forEach(e => {
                            newItems.push(Object.assign({}, deepCopy(already), e));
                        });
                    });
    
                    response = newItems;
                }
    
                else {
                    if (Array.isArray(response)) {
                        response.forEach(item => {
                            item = Object.assign({}, deepCopy(item), compressed);
                        })
                    }
    
                    else {
                        response = Object.assign({}, deepCopy(response), compressed);
                    }
                }
            });
        }
    
        return response;
    }
    

    Now you can call this routine in your code as follows:

    compress(data, "");
    

    Or if you want to see its output:

    // Prints the Output Object and uses 2 spaces as indentation
    console.log(JSON.stringify(compress(data, ""), null, 2));