[JAVA-2872] Provide an extension point for injecting a custom PropertySerialization implementation Created: 29/May/18  Updated: 10/Mar/23

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

Type: New Feature Priority: Major - P3
Reporter: Chris Birchall Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Epic Link: Investigate our POJO implementation
Quarter: FY23Q4, FY24Q2
Case:

 Description   

I have fields of type java.util.Optional in my POJOs, and I would like to avoid serialising the field if the value is an empty Optional.

It looks like I can do this using a custom implementation of PropertySerialization, but as far as I can tell there is no easy way to inject my custom implementation when using the PojoCodecProvider.

The only way I have found to do this is with some nasty reflection hackery, abusing a Convention to overwrite the propertySerialization field in all the PropertyModel s of the ClassModelBuilder:

class MyConvention implements Convention {
 
    private PropertySerialization<?> optionalAwarePropertySerialization = 
        new OptionalAwarePropertySerialization();
 
    @Override
    public void apply(ClassModelBuilder<?> classModelBuilder) {
        try {
            Class<PropertyModelBuilder> clazz = PropertyModelBuilder.class;
            Field propertySerialization = clazz.getDeclaredField("propertySerialization");
            propertySerialization.setAccessible(true);
            for (PropertyModelBuilder<?> propertyModelBuilder: classModelBuilder.getPropertyModelBuilders()) {
                propertySerialization.set(propertyModelBuilder, optionalAwarePropertySerialization);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            // error logging
        }
    }
 
}

I then register this convention when building the codec registry:

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
            MongoClient.getDefaultCodecRegistry(),
            CodecRegistries.fromProviders(
                    PojoCodecProvider.builder()
                            .automatic(true)
                            .conventions(Arrays.asList(CLASS_AND_PROPERTY_CONVENTION, ANNOTATION_CONVENTION, new MyConvention()))
                            .build()));

This works, i.e. the property is not written to the Mongo document if it is empty, and it is passed to the POJO constructor as null when reading back from Mongo. But I have trouble sleeping at night knowing this code is running in production



 Comments   
Comment by Chris Birchall [ 08/Jun/18 ]

set custom property serialization instances without the need for any reflection. Using the PropertyModelBuilder#propertySerialization method.

You're right! Sorry, don't know how I missed that method. I'll get rid of my reflection code and just use that.

You don't have to write anything to Bson via the Codec if its null, you can ignore it.

If you mean I could use the Optional codec given in the example in the doc, but just remove the writer.writeNull() line, unfortunately that doesn't work. Because the Bson writer has already started writing the field (I think it writes the field name first, IIRC?) before it calls the Codec. So if you don't write any value, you end up with invalid Bson.

how to handle the hydration of the Option when loading Bson data, to ensure it has a value

Yes, this is a problem. For now I've just added null checks in my POJO's constructors:

@BsonCreator
public MyPojo(@BsonProperty("string") String string, @BsonProperty("optional") Optional<String> optional) {
    this.string = string;
    this.optional = optional == null ? Optional.empty() : optional;
}

Not pretty, but not the end of the world.

Comment by Ross Lawley [ 08/Jun/18 ]

Hi Chris,

You don't have to write anything to Bson via the Codec if its null, you can ignore it. Its more how to handle the hydration of the Option when loading Bson data, to ensure it has a value. That cannot be handled by Codecs and would require the Java bean to have a default value.

Ross

Comment by Chris Birchall [ 08/Jun/18 ]

Hi Ross,

Thanks for the reply.

I had a look at the Optional codec example that you mentioned. As you say, with a codec there is no way to say "skip the field completely" - you have to write a null value. Unfortunately that doesn't work in my case, because I have a unique index on that field. I guess I could make a partial index to only enforce uniqueness on non-null values, but I'd rather not have to fiddle with the indexes just for the sake of the Java driver.

Comment by Ross Lawley [ 04/Jun/18 ]

Hi cb372,

Thanks for the ticket, you should be able to set custom property serialization instances without the need for any reflection.  Using the PropertyModelBuilder#propertySerialization method. A Convention is the correct level for manipulating the ClassModel and PropertyModel builders. So you should be able to achieve your aim with the current API.

However, there may be an alternative approach, in the generics support section of the documentation there is an example of creating a OptionalPropertyCodecProvider and a OptionalCodec implementation that could be adapted. However, a default value for the Optional is required with the codec approach.

I hope that helps,

Ross

Generated at Thu Feb 08 08:58:16 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.