Home > Net >  Get a filtered list of objects defined inside a MongoDB document
Get a filtered list of objects defined inside a MongoDB document

Time:09-29

I work with Spring Boot and MongoDB. I am looking for a solution to get a list of hobbies but not all of them. The hobbies have to meet the conditions (name or description have to include searched phrase).

@Document("persons")
@Data
class Person {
    @Id
    private String personId;
    private String name;
    private List<Hobby> hobbies;
}
@Data
class Hobby {
    private String name;
    private String description;
    private String notImportantField;
}

Example

I want to get a person with reduced list of hobbies (all the hobbies have to contain searched phrase in one of its field).

Person document from the database

{
    "_id" : ObjectId("id1"),
    "name" : "some person",
    "hobbies" : [
        {
            "name" : "A",
            "description" : "AB",
            "notImportantField" : "ABCDEF"
        },
        {
            "name" : "ABC",
            "description" : "ABCD",
            "notImportantField" : "ABCDEF"
        }
    ]
}

What I want to receive:

  • I want person with id id1 and I am looking for phrase ab in person's hobbies. I should get a list with 2 hobbies (first hobby's description contains ab, second hobby's name and description contains ab)
  • I want person with id id1 and I am looking for phrase d in person's hobbies. I should get a list with 1 hobby (second hobby's description contains d)

I tried something like this but I get a person with all of its hobbies.

@Repository
interface PersonRepository extends MongoRepository<Person, String> {
    @Query("{'$and': ["  
                "{'_id': :#{#personId}},"  
                "{'$or':["  
                    "{'hobbies.name': {$regex: :#{#searchPhraseRegex}, $options: 'i'}},"  
                    "{'hobbies.description': {$regex: :#{#searchPhraseRegex}, $options: 'i'}}"  
                "]}"  
            "]}")
    List<Person> method(@Param("personId") String personId, @Param("searchPhraseRegex") String searchPhraseRegex);
}

The result method should return a person with filtered hobbies or only list of hobbies. Thanks in advance for help.

UPDATE: RESOLVED

Thanks @user20042973 for the help :) I used your query and change it a little to match my query in Mongo repository. It works as I expected. The result method is:

@Repository
interface PersonRepository extends MongoRepository<Person, String> {
    @Aggregation(pipeline = {
            "{'$match': {'_id': :#{#personId}}}",
            "{'$addFields':  {'hobbies': {'$filter': {"  
                    "'input': '$hobbies', "  
                    "'cond': "  
                        "{'$or': ["  
                            "{'$regexMatch': {'input': '$$this.name', 'regex': :#{#searchPhraseRegex}, 'options': 'i' }},"  
                            "{'$regexMatch': {'input': '$$this.description', 'regex': :#{#searchPhraseRegex}, 'options': 'i' }}"  
                        "]}"  
                    "}}}}"
    })
    Optional<Person> findPersonByIdAndFilterHobbies(@Param("personId") String personId, @Param("searchPhraseRegex") String searchPhraseRegex);
}

CodePudding user response:

Helpfully, getting a filtered list of objects from an array can be done using the $filter operator. The operator takes an array as input and can process each item individually via an expression. In your situation that expression would include a $regexMatch to look for the value in the string(s) of interest. So the stage would look something like this:

  {
    "$addFields": {
      hobbies: {
        $filter: {
          input: "$hobbies",
          cond: {
            $or: [
              {
                $regexMatch: {
                  input: "$$this.name",
                  regex: "d",
                  options: "i"
                }
              },
              {
                $regexMatch: {
                  input: "$$this.description",
                  regex: "d",
                  options: "i"
                }
              }
            ]
          }
        }
      }
    }
  }

Sample Mongo Playground example is here.

From your question it's not quite clear if you will separately be using this as selection criteria for the document itself. I've left the leading $match stage to just use _id for the moment, but of course you can add any other filters to it (such as 'hobbies.name': /d/i) as needed.

I'm not personally familiar enough with Spring Boot to say what the exact syntax is to create such a pipeline, but I do know that aggregations are supported.

  • Related