-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Regression: 2.15.0 breaks deserialization for records when mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); #3906
Comments
Seems to work if you don't change the mapper visibility setting - see #3907. Would it be possible for you to not set this value? This issue will still be investigated. I'm just making the suggestion above as a workaround. I'm no expert on the benefits of setting the visibility to non-default values. |
Fix/workaroundAllow visibility for
ExplanationPreviously, Records deserialization is specially handled, resulting in some behavioural differences (mostly missing behaviours) compared to POJO deserialization. #3724 was done to address those differences, to close the gap between Records vs POJO de/serialization:
|
(Interesting to see the different config used to utilise Jackson as only a deserializer not serializer: here & #3897 🤔.) |
Thanks. This is used both for serialization and deserialization. It was the deserialization that failed - here the "assert that this will actually work with an empty {} object" boot-up verification - and it only failed with records. I need this config to work both for 2.14.2 and 2.15. Will that new suggested configuration do that just the same? That is: I want ONLY fields evaluated, with any visibility, for both classes and records. No methods whatsoever. These are DTOs, if people slap methods on it, I don't care: I want the data transferred. I guess I found that config somewhere sometime, and it have done exactly what I wanted for at least 6 years (used to be just classes, and then it also worked just as expected with records. Nice.) (PS: I cannot really "test if it works" with an alternate config, as this is a "foundation lib", there are at least 50 different random services that depend on this just working as it always has. I just wanted to upgrade to keep with the times! :-) That bit pretty hard, both this, and the 5M char limit for String: FasterXML/jackson-core#863 (comment) ) (PPS: The bullet 2 where "setVisibility(PropertyAccessor.ALL, Visibility.NONE) "hides" those "creators", because PropertyAccessor.ALL includes PropertyAccessor.CREATOR" on the face of it sounds like a clear bug to me. For a record, there is no other proper way to get this thing made, so they should probably not be included? At least not the canonical constructor?) |
One thing worth noting: Records are technically very different from POJOs/Beans, so there are challenges from Jackson implementation side. Especially regarding access to Record Fields; something that will start failing on newer JDKs. It is not certain handling can be fully unified. I agree that it is important to be able to have value types, handling that works for both 2.14 and 2.15. But we really did not realize that there was Record usage that relied on explicit Visibility settings -- there's no testing so one can view at as unsupported use case, technically. |
@cowtowncoder Thanks!
yes, I agree, which makes me wonder a bit about this plan of jamming records and POJOs into the same regime, as seems suggested by @yihtserns?
This is really not the point: The point is that I want to use the same ObjectMapper no matter what my users are choosing: POJOs (as they have for 8 years), or records (the new kid). There is thus a jumble of some POJOs here, and some records there, people now also migrating due to the extreme terse-ness and nice-ness of records. I feel it makes little sense to have to handle this differently? How should I even do that? A POJO could have a record inside it, and other way around. |
Right... and which is why code originally was separate. I had plans to rewrite Introspection of POJOs, Records, but didn't have time so others had a chance to try out alternatives here.
Sorry, I did not mean that as a justification for breaking things: but rather explanation of why this use case was not covered by tests. I understand it was used as a way to unify handling with 2.14 when there was no other way. I'd have to dig up where it was done, but the visibility checker defaults were forked for 2.15 (or was it 2.14 already?) and I think there's a way to change them separately as well. I just do not remember details. EDIT: I think I got confused with above: visibility checker defaults had to do with different defaults for JDK types, where we do NOT want to detect fields. Records (outside of JDK packages) not affected by that. |
Test result for
The only way the failing tests above can work is when |
The only way I know is this:
Alternatives
...which was the whole point of #3724's "jamming records and POJOs into the same regime", so that using Records is no (or not much) different from using POJO - the issue you're facing now is because Records & POJOs are handled similarly... |
@cowtowncoder Do you think we should copy that to
|
No, as I said at top, I have both of: ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); The full init is: ObjectMapper mapper = new ObjectMapper();
// Read and write any access modifier fields (e.g. private)
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
// Drop nulls
mapper.setSerializationInclusion(Include.NON_NULL);
// If props are in JSON that aren't in Java DTO, do not fail.
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Write e.g. Dates as "1975-03-11" instead of timestamp, and instead of array-of-ints [1975, 3, 11].
// Uses ISO8601 with milliseconds and timezone (if present).
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
// Handle Optional, OptionalLong, OptionalDouble
mapper.registerModule(new Jdk8Module()); It is here: https://github.com/centiservice/mats3/blob/main/mats-serial-json/src/main/java/io/mats3/serial/json/MatsSerializerJson.java#L120-L152 - now also with the attempt at handling the 5M chars-in-String limit. Note: I had accepted that I always need a no-args constructor (as opposed to GSON). (Note: If it is possible to support missing no-args constructor for a class, even with Java 17+, that would be excellent!)
Exactly. Which is why I said: "yes, I agree, which makes me wonder a bit about this plan of jamming records and POJOs into the same regime, as seems suggested by @yihtserns?" My point is that the construction of a Record and of a Class, and their population of fields, are rather different, so it sounds .. ambitious .. to do it with the same codebase/flow? Note, I do not try to dictate anything here, it is just an observation. But it would be nice if this - as seen from my side - regression - was not present! |
OK, was confused because it was commented out in the sample code. That means you might actually have 2 problems:
|
Yes, but I tried to mention:
It was an attempt to reduce the problem to the bare minimum. |
To summarize, there are 2 things broken when using this config:
1. For Records deserializationCaused by #3724 to make Records de/serialization behaves just like POJO de/serialization - it sees:
...similarly to:
Fixes/workarounds
2. For Records serializationBroken by #3737 - Records serialization will always result in empty JSON hash (i.e. #3894 to fix that is pending review. |
As for changing Jackson to make Records' creators always visibility regardless of config, I can/will create a PR if the core maintainers think that's the way to go. |
=1. Do you mean that this won't be solved "natively"? I must change the code? 1.: Will this work identical for 2.14 and 2.15? Why the Visibility.NON_PRIVATE? I want the no-args to be private if it needs be there. (Just curious: Why is a constructor called a creator, if this is what we're talking about?) However, I can't shake the feeling that this is a pretty clear regression. =2: |
@yihtserns thanks for looking at this this. Let's not rush things. We will need to tackle one problem at a time. Maybe getting #3894 progressed is the first priority. @stolsvik will just have to be patient while we resolve the issues. Please stick with Jackson 2.14 in the interim - or try another library, if you prefer. |
Well, I pretty obviously prefer Jackson, as otherwise I would probably not try to report this. |
Need to wait for the core maintainers to make the final decision.
Creators are constructors or factory methods (with name They are not related to, and nor will that config affects no-arg constructor.
You can use this with both
|
Hmmmh. I am bit hesitant wrt overriding users' explicit settings but it does make some sense -- esp. since that's in 3.0. |
I merged #3894 fwtw; shouldn't (I think) affect this issue but just in case. |
Just a comment from the side:
I agree to the general idea that this seems just wrong, but somehow you need to get to those constructors to actually be able to make records! Another way could be a special-case "if" when about to create a record, that effectively ignored the setting by just finding the canonical constructor in spite of the setting. However, you would at least not just "overwrite" the user-set setting?! |
@stolsvik Yeah this is the challenge in using same logic, handling as with POJOs, when rules are bit different (Records having clear, well-defined rules of what constitutes canonical Constructor, what properties exist). @yihtserns I think we should go with the patch you suggest to unblock remaining issues. |
Note: I had to adjust the suggestion a bit to get the "only adjust visibility for records" to work: The public VisibilityChecker<?> findAutoDetectVisibility(AnnotatedClass ac, VisibilityChecker<?> checker) {
if (ac.getType() == null) {
return checker;
}
if (!ac.getType().isRecordType()) {
return checker;
}
// If this is a Record, then increase the "creator" visibility again
return checker.withCreatorVisibility(Visibility.ANY);
} |
Another comment from the side: I still don't get why the patch suggest |
Yes:
...will be compiled into:
I just copied that directly from 3.x codebase. In hindsight, I should've changed that to |
I do not remember details here, but it is possible the idea was to avoid exposing |
Totally agree. You need to create an instance. That I have chosen to make this DTO-record an inner private entity of whatever service class it resides in, shouldn't make any difference. |
@yihtserns I think fix you suggested would be doable (just need to change check to use |
Note: need to figure out what (if anything) to do with 3.0 (master); 2 failing tests left. |
Describe the bug
This code used to work with 2.14.2, but not with 2.15.0
Version information
2.15.0
To Reproduce
Comment out the
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
and it works.Note that the commented-out
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
is included in my actual code to get intended behaviour (i.e. only fields are serialized and deserialized, no methods involved), but this did not make any to or from for the problem.Exception is:
The text was updated successfully, but these errors were encountered: