Home > front end >  How to filter on a value nested deeply in a complex array of objects?
How to filter on a value nested deeply in a complex array of objects?

Time:08-24

I am trying to filter a multi-nested complex array of objects on a specific value. For example, I want to filter a car models array according to the incoming parameter, say "bmw". The expected output should be an array of top level objects which has the incoming parameter (i.e. "bmw" here) as a value in any position of the complex object.

I have tried several approaches without success and I have posted two attempts below together with the output received.

Is there something wrong with my approach or is it my code?

    const arr = [
      {
      id:"1",
      name:"name1",
      assets: [
          {
          cars: {
              brand : [
                  {
                      model: "bmw"
                  },
                  {
                      model: "dodge"
                  }
              ]
          }
          }
      ],
      assets: [
          {
          cars: {
              brand : [
                  {
                      model: "ford"
                  },
                  {
                      model: "ferrari"
                  }
              ]
          }
          }
      ],
      },{
      id:"2",
      name:"name2",
      assets: [
          {
          cars: {
              brand : [
                  {
                      model: "audi"
                  },
                  {
                      model: "maseratti"
                  }
              ]
          }
          }
      ],
      assets: [
          {
          cars: {
              brand : [
                  {
                      model: "mercedes"
                  },
                  {
                      model: "bmw"
                  }
              ]
          }
          }
      ],
      }
    ]

If we use, for example:

let search="bmw"

then the expected output would be the following array of objects:

    [
      {
      id:"1",
      name:"name1",
      assets: [
          {
          cars: {
              brand : [
                  {
                      model: "bmw"
                  },
                  {
                      model: "dodge"
                  }
              ]
          }
          }
      ],
      },
      {
          id:"2",
          name:"name2",
          assets: [
          {
              cars: {
                  brand : [
                      {
                          model: "mercedes"
                      },
                      {
                          model: "bmw"
                      }
                  ]
              }
          }
          ],
      }
    ]

Attempt 1:

const arr = [{ id: "1", name: "name1", assets: [{ cars: { brand: [{ model: "bmw" }, { model: "dodge" } ] } }], assets: [{ cars: { brand: [{ model: "ford" }, { model: "ferrari" } ] } }],}, { id: "2", name: "name2", assets: [{ cars: { brand: [{ model: "audi" }, { model: "maseratti" } ] } }], assets: [{ cars: { brand: [{ model: "mercedes" }, { model: "bmw" } ] } }],}];

let search="bmw"
let filtered= [];
arr.filter((person)=>{
    person.assets.filter((car) => {
        car.cars.brand.filter((m) => {
            if(m.model.toLowerCase().includes(search.toLowerCase())) {
                filtered.push(m)
            }
        })
    })
});

console.log(arr);

/*********** console output:

[{
    assets: [{
        cars: {
            brand: [{
                model: "ford"
            }, {
                model: "ferrari"
            }]
        }
    }],
    id: "1",
    name: "name1"
}, {
    assets: [{
        cars: {
            brand: [{
                model: "mercedes"
            }, {
                model: "bmw"
            }]
        }
    }],
    id: "2",
    name: "name2"
}]

***********/

Attempt 2:

const arr = [{ id: "1", name: "name1", assets: [{ cars: { brand: [{ model: "bmw" }, { model: "dodge" } ] } }], assets: [{ cars: { brand: [{ model: "ford" }, { model: "ferrari" } ] } }],}, { id: "2", name: "name2", assets: [{ cars: { brand: [{ model: "audi" }, { model: "maseratti" } ] } }], assets: [{ cars: { brand: [{ model: "mercedes" }, { model: "bmw" } ] } }],}];

let search="bmw"
const q = arr.filter((element) => {
element.assets.filter((element1) => {
    element1.cars.brand.filter((m) => 
        m.model.toLowerCase().includes(search.toLowerCase()))
        .map(element=> {
            return Object.assign({}, element, {element1});
        })
    })
})

console.log(q);

/*********** console output:

[]

************/

CodePudding user response:

Having corrected the initially given arr structure, a solution is to drill into the structure with an outer call to .filter and inner calls to .some:

MDN Filter

MDN Some

const arr = [
  {
    id:"1",
    name:"name1",
    assets: [
      {
        cars: {
          brand : [
            { model: "bmw" },
            { model: "dodge" },
            { model: "ford" },
            { model: "ferrari" }
          ]
        }
      }
    ],
  },
  {
    id:"2",
    name:"name2",
    assets: [
      {
        cars: {
          brand : [
            { model: "audi" },
            { model: "maseratti" },
            { model: "mercedes" }
          ]
        }
      }
    ],
  }
];



function filterUsersByCar(carBrand, users) {
  return users.filter((user) => {
    const carAssets = user.assets.find((asset) => 'cars' in asset);
    return carAssets && carAssets.cars.brand.some(({ model }) => {
      return model === carBrand;
    });
  });
}

console.log(filterUsersByCar('bmw', arr));

Some things to note:

The inner carAssets is needed because the given arr structure implies that it might be possible that a user has something different than car models inside assets. This would mean that a script first has to find out if any of the items in assets deals with cars. The shown solution uses the logik "if any of the assets items has a field 'cars', the whole item is about cars". This leads to "if there is no 'cars' asset, the whole user should be excluded from the result set". I hope that is right.

If the user can only have assets that are cars, it might be possible to simplify the user structure (read: "item in arr") such that the assets property directly includes the objects which are currently placed inside assets.cars.brand. That would also remove the need for the hasCarAssets check. If that is an option, it would change the solution to:

const arr = [
  {
    id:"1",
    name:"name1",
    assets: [
      { model: "bmw" },
      { model: "dodge" },
      { model: "ford" },
      { model: "ferrari" }
    ],
  },
  {
    id:"2",
    name:"name2",
    assets: [
      { model: "audi" },
      { model: "maseratti" },
      { model: "mercedes" }
    ],
  }
];



function filterUsersByCar(carBrand, users) {
  return users.filter((user) => {
    return user.assets.some(({ model }) => model === carBrand);
  });
}

console.log(filterUsersByCar('bmw', arr));

  • Related