Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use @JacksonAnnotationsInside to reduce Mongojack configuration #213

Closed
msmerc opened this issue May 19, 2021 · 7 comments
Closed

Use @JacksonAnnotationsInside to reduce Mongojack configuration #213

msmerc opened this issue May 19, 2021 · 7 comments

Comments

@msmerc
Copy link

msmerc commented May 19, 2021

Hi,

Would you consider using clever jackson annotations to reduce the amount of object mapper magic you need to get mongojack to work. Here's what I'm thinking:

First define an annotation like so:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = ObjectIdSerializer.class)
@JsonDeserialize(using = ObjectIdDeserializers.ToStringDeserializer.class)
@JsonProperty("_id")
@JsonInclude(JsonInclude.Include.NON_NULL)
public @interface SmartId {}

Then use it like so:

      public static class Example {
        @SmartId
        public String id;
        public String text;
    }

    @Test
    public void simpleTest() {
        CodecRegistry provider = new JacksonCodecRegistry(
                new ObjectMapper(), //Look no modules or anything!
                db.getCodecRegistry(),
                UuidRepresentation.UNSPECIFIED
        );

        var coll = db.withCodecRegistry(provider).getCollection("test", Example.class);
        Example ex = new Example();
        ex.text = "fiddlesticks";
        var io = coll.insertOne(ex);
        Example ex2 = coll.find().first();
    }

The neat thing about this is that I've only used an out-of-the-box CodecRegistry, and an out-of-the-box ObjectMapper. This is particularly valuable for projects that have complex ObjectMapper setups.

@msmerc msmerc changed the title Use @JacksonAnnotationsInside to reduce Mongojack voodoo Use @JacksonAnnotationsInside to reduce Mongojack configuration May 19, 2021
@rlodge
Copy link
Contributor

rlodge commented May 19, 2021

I don't think I'm willing to change how the project works by default to something like this, as I don't want to force users of the project to annotate every field they need to serialize.

I'm not sure if what you're trying to accomplish requires a change in the project. Is there something in the project preventing you from doing what you describe? In the projects that I use MongoJack in I use a quite complex ObjectMapper setup without requiring any changes to MongoJack itself. (I basically ignore the module provided by MongoJack and just do the modifications I need to the ObjectMapper myself, but it's also possible to have some substantial control over how MongoJack bootstraps the module and makes changes to the ObjectMapper by using the methods in ObjectMapperConfigurer.

@msmerc
Copy link
Author

msmerc commented May 20, 2021

Thanks for your reply.

My logic is that the @ObjectId @Id annotations are a little confusing (which one should I use? When?), and also a little magical (only works if you install the MongoJack modules in your ObjectMapper). They could be replaced / merged into a single annotation that only needs to be applied to MongoDb Id fields.

There's nothing preventing me from doing this in my project - but specifically @JsonSerialize(using = ObjectIdSerializer.class) means pointing to a package named "internal" and I usually try to avoid doing this with 3rd party libraries.

There is one disadvantage to this proposed mechanism: the annotation needs to know about the serializer implementation. This means you need the whole mongojack package available. In an ideal world, MongoJack's annotations would be packaged separately to the library itself - which means the data objects could be defined without importing the MongoJack library.

@rlodge
Copy link
Contributor

rlodge commented May 20, 2021

It seems like what you're trying to do is combine the functions of @ObjectId and @Id.

Basically @Id is used as a shorthand for @JsonProperty("_id"), but adds additional behaviors related to extracting the ID value from the object for "find-by-id" style queries or updates. I can see there would be a distinct advantage to adding @JsonProperty("_id") and @JacksonAnnotationsInside to the @Id annotation, because it would then make the property name mapping work as expected with a vanilla object mapper (though I have projects where I map it in two ways: for the DB it's mapped as _id but when the same object is returned over HTTP it's id, and adding the meta-annotations would break that functionality.

The @ObjectId basically says that this field, whether or not it's an _id field, should be treated by the DB as an ObjectId, and represented as declared (string or byte[]) in the bean. It's supposed to work for DBRefs too.

The proposed @SmartId annotation basically combines those functions: renaming the field and changing the representation. It has a couple of disadvantages, though: it fixes the java representation as a string; it won't work with the functionality in places like org.mongojack.JacksonMongoCollection#save(TResult, com.mongodb.WriteConcern) or com.mongodb.internal.operation.Operations#insertMany; and as you point out it would make it impossible to separate the annotations from the implementation (which I should do when I get the chance).

I'm not sure I see an obvious way around those disadvantages.

@msmerc
Copy link
Author

msmerc commented May 21, 2021

Ok I see what you're saying. It's certainly not clear cut.

I guess one thing I'm trying to do is reduce the sea of annotations that you get when you have several 3rd party libraries in your project. Jackson has @JacksonAnnotationsInside that you mention. Does mongojack honour that? If so I can roll my own annotations and get by that way?

@rlodge
Copy link
Contributor

rlodge commented May 21, 2021

Jackson should honor any annotations it would usually honor. I can verify that the current code will honor something like this:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Id
@ObjectId
public @interface IdProxy {
}

If you annotate your string id field, or your getter/setter with @IdProxy, it will save the field with the name _id and convert a string you set into that field to and from an ObjectId, presuming the string you set is correctly. If you leave the field null, mongo will generate one when you insert the object, and it will get correctly mapped on retrieval.

What it won't do is the functionality in org.mongojack.JacksonMongoCollection#save( or similar. The resulting behavior might seem a little unpredictable: the annotated field will not be filled out on save or insert, and the save() method will always assume it's an insert rather than an update.

I'll see if I can find a way around that, but that's the current behavior.

rlodge added a commit that referenced this issue May 21, 2021
@rlodge
Copy link
Contributor

rlodge commented May 21, 2021

I updated the way the codec retrieves the annotations, and it seems to be able to retrieve the annotations when used via @JacksonAnnotationsInside. I published a 4.2.1-SNAPSHOT version if you would like to try it and let me know if it works.

@msmerc
Copy link
Author

msmerc commented May 21, 2021

Wow that looks exactly what I need - thanks!

@rlodge rlodge closed this as completed Jul 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants