Skip to content
Advertisement

Ignore $in if empty array is provided

I have the following model:

export type IMangaModel = Document & {
  _id: string;
  title: string;
  description: string;
  demographic: Array<string>;
  genre: Array<string>;
  tags: Array<string>;
  related: Array<IMangaModel>;
  cover_image: {
    extraLarge: string | null;
    large: string | null;
    medium: string | null;
  };
  banner: string | null;
  al_id: number;
  al_url: string;
  likes: Array<IUserModel['_id']>;
};

Note: interface matches the model exactly but i pasted this as its much shorter.

I am using the following data to filter through this collection:

includeFilters: {
    genres: Array<string>;
    tags: Array<string>;
    demographics: Array<string>;
  };
  excludeFilters: {
    genres: Array<string>;
    tags: Array<string>;
    demographics: Array<string>;
  };

What i want to achieve is to find every document and check if the corresponding array has at least one of the values from the array i sent via includeFilters. While it has none of the values sent via excludeFilters.

To do this i used the following query:

const results = await this._manga
      .find(
        {
          tags: {
            $elemMatch: {
              $in: data.includeFilters.tags,
              $nin: data.excludeFilters.tags,
            },
          },
          genre: {
            $elemMatch: {
              $in: data.includeFilters.genres,
              $nin: data.excludeFilters.genres,
            },
          },
          demographic: {
            $elemMatch: {
              $in: data.includeFilters.demographics,
              $nin: data.excludeFilters.demographics,
            },
          },
        },
        { al_id: 1 }
      );

This seems to works fine as long as all of the arrays in includeFilters array have at least one value. But if an empty array is provided then no matches are found with $in, from what i understand $in requires at least one value to be present, but as nothing is provided then nothing is found. On the other hand, $nin works fine (at least i’d like to think so) because its not trying to exclude any values.

What i would like to achieve is that if an empty array is provided to $in then it outright just skips through that filter and doesnt look at it. When doing this in mysql if an empty array is provided then its ignored and every record is returned as a result. This is what i want with mongodb.

What i have tried till now are a couple of things.

First i tried to just add every possible value to array’s that are empty, this did not work for one reason, not all documents have the tags,genres and demographic data. So in the end nearly 90% of all documents are not included in the result.

Second thing i tried was to enable the ignoreUndefined option in the connection options. And then when creating the query check if the length is 0, if it is, pass undefined to $in. This did not work.

Third thing i tried was going through every possible scenario where some array is empty, this was bad for obvious reasons so i scratched it.

Fourth thing i tried was to make a function to build a dynamic query of sorts, this also became very messy because of the amount of possibilities.

Fifth and last thing i tried was using $or with the idea that the empty results would somehow be skipped.

Advertisement

Answer

To make this work better with TypeScript you could use Nenad’s approach with ES6 methods

const filters = Object.fromEntries(
  Object.entries(body).flatMap(([filterType, filters]) =>
    Object.entries(filters)
      .filter(([, values]) => values.length)
      .map(([key, value]) =>
        filterType === "includeFilters"
          ? [key, { $elemMatch: { $in: [...value] } }]
          : [key, { $elemMatch: { $nin: [...value] } }]
      )
  )
);
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement