Search code examples
mongodbspring-bootmongotemplate

Mongo query to filter inner Arraylist items in Spring Boot using Mongo Template


Below is my DOCUMENT:

@Document(collection = "products")
@Data
@EqualsAndHashCode
public class Product {

    @Id
    private String id;

    @Field("lang_content_list")
    private List<ProductLangContent> contentList;

    @Data
    public static class ProductLangContent {
        @Field("lang")
        private String lang;
    }

}

I want to get only those contentList where lang = 'en'. lang is unique within innner list.

Note: I am using Mongotemplate

My sample json is:

{
    "_id" : ObjectId("5d2040f9f7c5ac1e9d8ef712"),
    "lang_content_list" : [ 
        {
            "lang" : "en"
        }, 
        {
            "lang" : "np"
        }
    ]
    "_class" : "com.sn.application.model.Product"
}

Desired query result is:

{
    "_id" : ObjectId("5d2040f9f7c5ac1e9d8ef712"),
    "lang_content_list" : [ 
        {
            "lang" : "en"
        }
    ]
}

I tried couple of queries but got no luck:

Aggregation aggregation = newAggregation(
                   project().and(filter("contentList")
                     .as("item")
                     .by(valueOf(
                          "item.lang")
                           .equalToValue(
                          "en")))
                  .as("contentList")
        );
        List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();

output is: contentList is null.

Tried:

Criteria elementMatchCriteria = Criteria.where("contentList").elemMatch(Criteria.where("lang").is("en"));

It gives all elements in contentList. I don't want that. I want only one object in inner list where lan = 'en' .

Huge Thank you in advance.

Tried:

AggregationOperation match = Aggregation.match(Criteria.where("contentList.lang").is("en"));
        AggregationOperation unwind = Aggregation.unwind("contentList");
        AggregationOperation group = Aggregation.group("id")            
                .push("contentList").as("contentList");

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(match);
        operations.add(unwind);
        operations.add(match);
        operations.add(group);
        Aggregation aggregation = Aggregation.newAggregation(operations);
        List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();
        System.out.println(results.get(0).getContentList() != null);

output is: false. Inner array object is coming as null.


Solution

  • Your document has an array field "contentList" which will be having multiple "lang". I'm assuming you want to filter/get all those documents in which atleast one "lang" in "contentList" is "en". Then use :

    Criteria elementMatchCriteria = Criteria.where("contentList.lang").is("en"));
    

    If you want only that object in the inner array where lang='en', you need to use aggregation pipeline like:

    Link: https://mongoplayground.net/p/JaJ7420i4qJ

    db.collection.aggregate([
      {
        $match: {
          "lang_content_list.lang": "en"
        }
      },
      {
        $unwind: "$lang_content_list"
      },
      {
        $match: {
          "lang_content_list.lang": "en"
        }
      },
      {
        $group: {
          "_id": "$_id",
          "_class": {
            $first: "$_class"
          },
          "lang_content_list": {
            $push: "$lang_content_list"
          }
        }
      }
    ])
    

    The reason for using the last group stage is that in your object, contentList is an array so we need to wrap lang object as array, otherwise not needed if you can change return type object.

    In Spring MongoTemplate code:

    AggregationOperation match = Aggregation.match(Criteria.where("lang_content_list.lang").is("en"));
    AggregationOperation unwind = Aggregation.unwind("lang_content_list");
    AggregationOperation group = Aggregation.group("_id")             
            .first("_class").as("_class")                
            .push("lang_content_list").as("lang_content_list");
    
    List<AggregationOperation> operations = new ArrayList<>();
    operations.add(match);
    operations.add(unwind);
    operations.add(match);
    operations.add(group);
    Aggregation aggregation = Aggregation.newAggregation(operations);
    List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();