-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Serialization behavior changed in 2.14 when mixing @JsonIgnore
and @JsonProperty
on different levels of class hierarchy and/or accessors
#3722
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
Comments
Some things that I can comment in, which I hope might be useful in understanding what you see:
I also wonder if it might be good to actually split this up into separate issues for fields where there is observed difference in behavior. |
Ok. So, on Fundamentally, however, what happens is that due to merging/flattening of annotations (which is done in general way with no knowledge of semantics of annotations, and especially without relationships between ignore/property) both |
And So there is the definite difference between 2 cases:
Rules also differentiate between explicitly annotated case (annotation indicates inclusion or ignoral) and "implicit" cases (accessor is Visible): implicit case is, by default, included; but it will not change explicit annotations.
|
And then And... |
It seems to me that the behavior, at least for these 4 cases, is as I'd expected and as intended, in 2.14.0. The case of single accessors having both But in your case there is further complication of different accessors, not just overrides over inheritance hierarchy. |
Hi @cowtowncoder , Really appreciate your detailed response! TBH, I always thought that in Jackson, if you override a method, you also override all of its annotations, and if you want to preserve them, you have to redefined them. Maybe I may have gotten it wrong... I think it's kinda counter intuitive, because it doesn't allow to define scenarios like you described in your comment above (e.g. having I think what bothers me most is the fact that behavior has changed (specifically for the case of I'm trying to understand what other changes I should expect to behavior following the changes in BTW, specifically regarding |
@RB14 Right, annotation merging is done on per-annotation basis, not on per-method -- so overriding one annotation does not override all. In my opinion this is better as it avoids having to always add each and every annotation from base types (esp. interfaces). But be that as it may, it has always been this way. As to change in behavior: I was surprised that anyone would ever add conflicting definitions ( Still: as far as I know, there is just one, single, specific change (in #3357). It may have bigger effect in your codebase of course, but fundamentally it seems like a relatively limited change for user base overall. On inclusion: first, as I said, parent/child relationship does not matter across annotations, only "within". So, if child has same annotation as parent then child one overrides parent setting. So why did 2.13 include And yes, looking at code, I think change is what I expected to achieve. It also seems like what your example attempted to do (child having One thing I will do is to add a note on |
@JsonIgnore
and @JsonProperty
on different levels of class hierarchy@JsonIgnore
and @JsonProperty
on different levels of class hierarchy and/or accessors
@cowtowncoder, Thanks once again fro the detailed response and for your engagement! I totally agree that the new behavior makes a lot of sense, and actually it might be a good thing because it exposed this wrong usage we had in our code, which shouldn't have worked to begin with. So my only concern is with the implicit change in behavior (after such a long time it behaved like that). We are probably using Jackson wrong in several other places, but I have to admit that we have a really huge code base, which also contains many legacy parts, some of which are not 100% covered by unit/integration tests, so it's going to be very difficult to fix everything to behave correctly. However, that being said, I completely accept and agree with your reasoning. Especially the part where you talked about what should be considered "expected" behavior in general... if you don't have unit test for this, and you didn't think about it, and it's not documented as part of the official semantics of the library, we should not considered it to be "expected". This is fair. Regarding:
Well, I think at least one valid use-case would be to override a To decide on the best course of action for my team, I would like to ask a few questions:
Adding a note to the release notes of 2.14 would be great! Appreciate all your help here! |
Ok, yes, fair enough: I do agree that it would be intuitive that annotations at different levels in hierarchy would be taken into account. And given complexity of actual resolution it is possible to assume different logic from observed behavior. Anyway, the big challenge in supporting things like inheritance-aware application of annotations is dimensionality: annotations are considered along (at least?) 3 dimensions:
So system that takes into account the fact that, say, Anyway, back to
whereas with 2.14 Field's status is determined to be only implicitly included (in absence of any explicit annotations included). At least I think that is how that's resolved. And then, finally, to the question of how to find problem cases in your codebase: yes, I think you got it right -- the only changed case should be one where there are both |
@cowtowncoder , thanks so much for all this discussion! I have no further questions at this point, but really appreciate all your help. We've started adapting our code to the new behavior, and hopefully we will find all broken cases fast enough. For the time being though we decided to force a downgrade of Jackson to 2.13.4, but we are actively working on adapting our code to 2.14. Feel free to close. Thank you! |
Sounds good @RB14. |
Describe the bug
In our projects we rely very heavily on Jackson. In one of our recent dependency upgrades, Jackson was bumped from version
2.13.3
to2.14.0
, and we started seeing some odd behavior (mostly in serialization, but it's unclear if this affect deserialization as well). I believe the problems are related to #3357.The problems manifest when there is a mix of a public field and a getter, and most of the times, it also involves a base class (possibly abstract), that defines the getters as well (as abstract). It happens when there is a mix of
@JsonIgnore
and@JsonProperty
/@JsonView
on either of the field/concrete getter/abstract getter (e.g.@JsonProperty
on the abstract getter, and then@JsonIgnore
on the overridden getter). I had to write a small test to demonstrate the issues... this test demonstrate only some of the issues we experienced, but there could be other issues, since, if I understand correctly, you'v changed the logic to decide when to serialize in cases where there is a mix of@JsonIgnore
and@JsonProperty
. I must say that, in some cases, when there is inheritance involved, it makes sense to have this kind of mix if the different annotations are added in different levels in the class hierarchy.See test how to reproduce below.
Version information
2.14.0
To Reproduce
I ran the following test both with version
2.13.3
and2.14.0
:Results:
2.13.3
:{"field1":"field1","field2":"field2","field3":"field3Getter"}
2.14.0
:{"field2":"field2","field3":"field3Getter"}
The interesting things to note here:
field1
andfield4
- they have the exact same annotations on the field and getters, but the only difference is thatfield1
is also defined in the base class (with@JsonProperty
) andfield4
is not. In version2.13.3
,field1
was serialized, whereas in2.14.0
it is not, whereas in both versionsfield4
is not serialized. It seems that the annotation overgetField1
in the base class somehow affected serialization of the child class, even though we overrode it (so I would expect the annotation in the child class to win). In2.14.0
this works "correctly", but while this behavior is more correct, it's still a big change in semantics (which might be considered a breaking change actually). Notice that there is no annotation onfield1
itself (there no explicit@JsonProperty
there), so basically we relied on the default behavior.field2
is exactly likefield1
, except that it has an explicit@JsonProperty
annotation on the field. In this case, both versions decided to serialize it. So I'm raising a question, what is the difference between explicitly setting the@JsonProperty
annotation (field2
) to not setting it at all, and using the default semantics (field1
)? Our code relies on the default behavior in lots of places (unfortunately...)field3
is the opposite case offield1
- the field has an explicit@JsonIgnore
annotation, whereas the getter does not have any annotation (we rely on the default behavior here, again). Apparently both versions serialize it, and treat it as if it has a@JsonProperty
annotation (maybe because of the base class? either way it's very confusing, especially when comparing it to the reverse direction, which isfield1
, and to the fact that if I override a method I expect not to inherit its annotations).These are just a few samples, but of course I didn't test cases where the base class methods don't have annotations (in my tests all of them have
@JsonProperty
, what if they didn't have this set explicitly, and just rely on the default behavior?). And I haven't tested how this affects deserialization (like, what would happen if there is a contradiction between the annotations on the field and the annotations on the setter? or contradiction between an abstract setter and the concrete setter?)Expected behavior
TBH it's unclear what should be the correct behavior, as there are many kinds of scenarios. Notice that, as I wrote above, it might make sense that the behavior for
field1
will be the one implemented in2.14.x
, but I can't say the same aboutfield3
(even though it's identical in both versions, I would expect some consistency in behavior between the 2 symmetrical cases offield1
andfield3
). In any case, since the behavior used to be like in version2.13.x
for a very long time, and now it has changed, I think we should consider this a breaking change, and considering the potential risks it impose, maybe worth reverting it to the original behavior. Accepting the changes in2.14.x
would require us to do (probably) lots of changes in our huge code base... and of course understanding the new behavior, and how it's different from the behavior in previous versions.The text was updated successfully, but these errors were encountered: