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

Handle Enum introspection of values and aliases via AnnotatedClass instead of Class<?> #3832

Merged
merged 55 commits into from
Jun 16, 2023

Conversation

JooHyukKim
Copy link
Member

@JooHyukKim JooHyukKim commented Mar 18, 2023

Motivation

This PR provides more information to AnnotationIntrospector for Enum introspection by passing in MapperConfig and AnnotatedClass.

Prior to this PR, Enums have been directly handled from class declaration itself, using Class.getDeclaredFields()(🔗 check link for reference). Old way of handling Enums not only creates isolation from Jackson's rich functionality, but also down to the kind of basic functionalities such as simple mix-ins which created issues such as #2787.

Modifications

  • AnnotatedFieldCollector : consider enums as fields.
  • new API AnnotationIntrospector.findEnumValues(MapperConfig, Enum[], String[], AnnotatedClass)`.
  • new API AnnotationIntrospector.findEnumAlias(MapperConfig, Enum[], String[], AnnotatedClass)`.
  • BasicSerializerFactory : Remove enum self-rerence when handled via JsonFormat.Shape.Object
  • BasicDeserializerFactory : Pass in more information for EnumResolver construction

Original PR message (just for reference)

This pull request resolves issue #2787 by allowing mix-ins for Enum classes new 
EnumResolver.constructForMixIn(DeserializationConfig config, Class<?> enumCls, Class<?> mixInCls).

@cowtowncoder
Copy link
Member

Ok, so I probably should have mentioned earlier that I do not want mix-in handling logic to be added outside of where it is done currently. So (de)serializers, factories that produce them should ideally not have to know anything about existence of mix-ins but would rather just look for annotations. Part of the challenge is that otherwise EnumResolver etc need to duplicate lookups for a growing set of annotations.

We can leave this PR open, just in case my thinking evolves. I realize that structure of Enum types as bytecode is kind of interesting and challenging to reconcile with POJOs, for example.

@JooHyukKim
Copy link
Member Author

JooHyukKim commented Mar 19, 2023

I realize that structure of Enum types as bytecode is kind of interesting and challenging to reconcile with POJOs,

@cowtowncoder Thinking further, I think what you saying is this -- Why @JsonProperty and @JsonAlias annotations from a mix-in class do not override those from the deserialization target class for Enum types, as Enum classes have a different nature than POJOs that prevents using POJO mix-in handling on them.

@JooHyukKim
Copy link
Member Author

@cowtowncoder
Copy link
Member

Ok aside from that @JsonFormat thing that needs to go elsewhere, this looks like it might work in general. I don't think we can get this in 2.15 (too risky at this point wrt API changes) but can continue work.
I'll add some smaller notes for changes.

@JooHyukKim
Copy link
Member Author

in 2.15 (too risky at this point wrt API changes) but can continue work.

Great, sounds like a plan! 👍🏻 For the meantime, is there anything I can do to make our change more stable?

@cowtowncoder
Copy link
Member

in 2.15 (too risky at this point wrt API changes) but can continue work.

Great, sounds like a plan! 👍🏻 For the meantime, is there anything I can do to make our change more stable?

No, just need to figure out how to remove special handling from AnnotatedFieldCollector (to resolve test failures some other way)

@JooHyukKim JooHyukKim requested a review from cowtowncoder March 27, 2023 04:47
@JooHyukKim JooHyukKim marked this pull request as draft May 22, 2023 15:06
@JooHyukKim JooHyukKim marked this pull request as ready for review May 26, 2023 15:02
@JooHyukKim JooHyukKim changed the title Allow mixins for Enums Handle Enum values and aliases with Annotated class May 28, 2023
@JooHyukKim JooHyukKim changed the title Handle Enum values and aliases with Annotated class Handle Enum values and aliases with AnnotatedClass May 28, 2023
@JooHyukKim JooHyukKim changed the title Handle Enum values and aliases with AnnotatedClass Handle Enum values and aliases via AnnotatedClass instead of Class May 28, 2023
@JooHyukKim JooHyukKim changed the title Handle Enum values and aliases via AnnotatedClass instead of Class Handle Enum values and aliases via AnnotatedClass instead of Class<?> May 28, 2023
@JooHyukKim JooHyukKim changed the title Handle Enum values and aliases via AnnotatedClass instead of Class<?> Handle Enum introspection of values and aliases via AnnotatedClass instead of Class<?> May 28, 2023
@JooHyukKim
Copy link
Member Author

JooHyukKim commented May 28, 2023

I shifted direction of this PR and modified PR title and description accordingly, after studying this issue.

May I ask for your opinion, @cowtowncoder?

@cowtowncoder
Copy link
Member

@JooHyukKim I hope to get back to this one Really Soon Now -- and I think your approach looks good from what I see.
But I do want to review this well.

Thank you once again for doing such a good job in taking feedback and finding ever improving solutions. I REALLY appreciate your diligence and persistence.

public String[] findEnumValues(MapperConfig<?> config, Enum<?>[] enumValues, String[] names,
AnnotatedClass annotatedClass) {
// First collect all JsonProperty.value()
HashMap<String, String> enumToPropertyMap = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's retain order by using LinkedHashMap

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍🏻

for (AnnotatedField field : annotatedClass.fields()) {
JsonProperty property = field.getAnnotation(JsonProperty.class);
if (property != null) {
enumToPropertyMap.put(field.getName(), property.value());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check for empty name ("")? If existing code doesn't check, that's fine I guess but I think often happens when just adding @JsonProperty as-is (useless annotation for Enums of course), or specifying other properties but not value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍🏻 Revised(moved) this check to right before adding it to the Map.

Done, in accordance with your other comment 👍🏻

}
final Class<?> enumType = annotatedClass.getRawType();
HashMap<String,String> expl = null;
for (Field f : enumType.getDeclaredFields()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why iterate over these again? Didn't AnnotatedClass.fields() have everything we need?

Copy link
Member Author

@JooHyukKim JooHyukKim Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you are right about that AnnotatedClass.fields() does and "should" have everything we need. This one was from previous implementation and no longer needed.

Removed this whole iteration starting from....

      final Class<?> enumType = annotatedClass.getRawType();

..... here ✌🏼

continue;
}
String n = enumToPropertyMap.get(f.getName());
if (n == null || n.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, so empty String is handled here. Could have just not added it in Map earlier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍🏻 Revised(moved) this check to right before adding it to the Map.

Copy link
Member

@cowtowncoder cowtowncoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, added a minor comment. Hoping to approve and merge tomorrow. Phew!

@JooHyukKim
Copy link
Member Author

Looks pretty good, added a minor comment. Hoping to approve and merge tomorrow. Phew!

Thanks, 😆👍🏻 I applied all your comments (became much cleaner)

Copy link
Member

@cowtowncoder cowtowncoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@cowtowncoder cowtowncoder merged commit 2134584 into FasterXML:2.16 Jun 16, 2023
@JooHyukKim JooHyukKim deleted the 2787-allow-mixins-for-enums branch June 16, 2023 11:12
@JooHyukKim
Copy link
Member Author

JooHyukKim commented Jun 16, 2023

LGTM!

Thank you again for the thorough and solid insightful reviews, @cowtowncoder!

FYI, I plan on looking around in the project where we can leverage the new enum-related AnnotationIntrospector API. And also move away from using now-deprecated APIs.

Plz let me know if you need a hand merging this PR into master, or have any other suggestions or ideas. 🙏🏼🙏🏼

@cowtowncoder
Copy link
Member

@JooHyukKim Agreed. I need to know merge 2.16->master, but I did mark old methods as deprecated and ideally should retrofit methods that call it to avoid them wherever possible.

@cowtowncoder
Copy link
Member

@JooHyukKim Was able to merge it.

Noticed one other follow-up item: AnnotationIntrospector.findDefaultEnumValue() also takes Class, not AnnotatedClass.
So mix-ins wouldn't work.

But I guess it's not a big deal at this point.

@JooHyukKim
Copy link
Member Author

Noticed one other follow-up item: AnnotationIntrospector.findDefaultEnumValue() also takes Class, not AnnotatedClass.

I wrote an issue #3990 to keep more PRs in one place 👍🏻. Will add this one also.

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

Successfully merging this pull request may close these issues.

3 participants