[JAVA-4268] Projection didn't work for sub document using @BsonProperty Created: 30/Jul/21  Updated: 27/Oct/23  Resolved: 09/Aug/21

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Unknown
Reporter: Loïc MATHIEU Assignee: Valentin Kavalenka
Resolution: Works as Designed Votes: 0
Labels: external-user
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Documentation Changes: Not Needed

 Description   

I have a Test object that have an embedded class SubTest.

 

public class Test {
    public String field;
    public String unwanted;
    public SubTest subtest;
 
    public static class SubTest {
        public String subField;
        public String subUnwanted;
    }
}

And a projection class that I want to use with a projected query.

 

public class TestProjection {
    public String field;
 
    @BsonProperty("subtest.subField")
    public String subField;
}

When I try to list all the document with a projection query that contains the fields "field" and "subtest.subField", the query succeed but the subField field is null.

See the following example code snippet:

MongoClient mongoClient = MongoClients.create();
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));
MongoDatabase database = mongoClient.getDatabase("test").withCodecRegistry(pojoCodecRegistry);
MongoCollection<Test> collection = database.getCollection("test", Test.class);
 
Test test = new Test();
test.field = "field";
test.unwanted = "unwanted";
Test.SubTest subTest = new Test.SubTest();
subTest.subField = "subField";
subTest.subUnwanted = "subUwanted";
test.subtest = subTest;
 
System.out.println("Inserting the element");
collection.insertOne(test);
 
System.out.println("Listing all elements");
collection.find().cursor().forEachRemaining(System.out::println);
 
System.out.println("Listing all elements with a projection");
MongoCollection<TestProjection> projectedCollection = collection.withDocumentClass(TestProjection.class);
projectedCollection.find()
        .projection(Projections.include("field", "subtest.subField"))
        .cursor()
        .forEachRemaining(System.out::println); ///here subtest.subField is null but should not be

 



 Comments   
Comment by Valentin Kavalenka [ 09/Aug/21 ]

Hi loikeseke@gmail.com,

I think the driver works as expected in the experiment you described. @BsonProperty simply allows an "alternative document key name when converting the POJO field to BSON", i.e., writing

@BsonProperty("subtest.subField")
public String subField;

means that the document field name "subtest.subField" is mapped to the POJO field name subField.

Executing

getCollection("test")
    .find()
    .projection(Projections.include("field", "subtest.subField"))
    .cursor()

results in issuing the following find command:

{
  "find": "test",
  "filter": {},
  "projection": {
    "field": 1,
    "subtest.subField": 1
  }
}

which, in the experiment you described, produces a cursor with a single document

{
  "_id" : ...,
  "field" : "field",
  "subtest" : {
    "subField" : "subField"
  }
}

This document does not have the "subtest.subField" field, instead it has the "subtest" field which value is a document with the "subField" field, consequently TestProjection.subField is null. In order to decode the document as TestProjection, one first needs to project it to have fields "field" and "subField". We can do this by using an aggregation expression, specifically an expression object. The find command we want is

{
  "find": "test",
  "filter": {},
  "projection": {
    "field": 1,
    "subField": "$subtest.subField"
  }
}

and can be issued by executing the following code

getCollection("test")
    .find()
    .projection(Projections.fields(
            Projections.include("field"),
            Projections.computed("subField", "$subtest.subField")))
    .cursor()

With the above in mind, we can modify the experiment such that it decodes TestProjection as expected, and even still use @BsonProperty to display its purpose:

public static class TestProjection {
    public String field;
    @BsonProperty("subFieldWithDifferentName")
    public String subField;
 
    public String toString() {
        return "TestProjection{" + "field='" + field + '\'' + ", subField='" + subField + '\'' + '}';
    }
}

MongoCollection<TestProjection> projectedCollection = collection.withDocumentClass(TestProjection.class);
projectedCollection.find()
        .projection(Projections.fields(
                Projections.include("field"),
                Projections.computed("subFieldWithDifferentName", "$subtest.subField")))
        .cursor()
        .forEachRemaining(System.out::println); // prints "TestProjection{field='field', subField='subField'}"

Comment by Jeffrey Yemin [ 30/Jul/21 ]

Hi loikeseke@gmail.com,

Thanks for reaching out. We will have a look and get back to you soon.

Generated at Thu Feb 08 09:01:38 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.