Jackson Version: 2.9.8 I did check 2.9.10 and LATEST (which I believe to be 2.11) and the problem persists
This is my first time reporting an issue, so sorry in advance for any mistake or point in which I could have done better. Here it is:
- I am using Open Feing with a Jackson decoder to read my responses into a simple data class
public class V2AccessTokenResponse {
protected boolean success;
protected String oAuthToken;
protected String expirationDate;
}
-
I noticed that one of the fields - oAuthToken - was getting deserialized as null, I double-checked to see if I had a typo in my code or if the API was returning the field with a different name, it wasn't.
-
Digging into the code I found that during BeanDeserializer.vanillaDeserialiazer, the _beanProperties._hashArea array holds the property name as oauthToken (without the capital A)
- Believing this to be a bug I did the following tests:
- Adding @JsonProperty("oAuthToken") to the property, this corrects the issues and the deserialization works fine.
- Creating a new property aTestAgain, to see if the problem would happen, it did, as per the screenshot
- Diving into the code, I found the problem to be this, during the POJOPropertiesCollector.collectAll() method call, the addFields() call (line 308) add all the properties correctly, while the _addMethodsCall() adds the properties without the capitalized letters, this happens during BeanUtil.legacyManglePropertyName(), where the code tries to generate a proper property name by decapitalizing all letters after _set. This seems a good time to mention I am using Lombok to generate getter and setters and as such my setter was setOAuthToken, which got transformed to oauthToken. After that collectAll() would remove the correctly named properties, which caused the bug.
PS.: Now that I have traced it, I am in doubt, is it still a bug? Or should it behave like that?
I am sorry if this doesn't belong here and would be happy to provide more info if needed.
Comment From: cowtowncoder
Ok, first things first: only names given by "getters" and "setters" are modified in some ways.
Names of fields are not modified at all, nor explicit names from annotations like @JsonProperty
.
So, the question has to relate to a setter name.
From that, Jackson's heuristics for extracting intended name is almost same as standard Bean Naming, with just one exception: Jackson does lower-case leading sequence of capital letters. Standard bean naming would ONLY lower-case a single capital letter that is followed by a lower-case letter.
But you can change the default behavior by disabling
MapperFeature.USE_STD_BEAN_NAMING
which may be what you are looking for.
There are still other cases where problems are more complicated (between field names, setter/getter names), but I want to rule out this possibility first.
Comment From: victorgcapone
Okay, so here is the mapper, encoder and client definition
And the result:
Same as before, I went ahead and set USE_STD_BEAN_NAMING to true and:
As you can see, things got reversed, instead of oauthToken I get OAuthToken
Comment From: cowtowncoder
@victorgcapone I am not sure if I read what I wrote since this information is not what I asked about.
Comment From: victorgcapone
@cowtowncoder I am sorry, maybe I didn't understand what you want, would you mind explaining? I thought the problem was related to the standard bean naming and you wanted me to disable it to see if that solved the problem.
Comment From: cowtowncoder
Ah ok. I think my instructions were incomplete here. Could you please include full definition of data class (V2AccessTokenResponse?) you have? If you are using Lombok or Immutables or one similar frameworks, I would need to know which one -- they do quite a bit of their own processing. So, if (f.ex) using Lombok, it would be necessary to know full definition after it has processed the bytecode: for Immutables, auto-values, would need to see implementation class(es) generated. They often include additional annotations as well as generated actual getter/setter methods.
Comment From: victorgcapone
Ok, I will include both code and the generated .class, as I said I am only using Lombok
Code:
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@Getter
@Setter
public class V2AccessTokenResponse {
protected boolean success;
protected String oAuthToken;
protected String expirationDate;
}
Generated .class
public class V2AccessTokenResponse {
protected boolean success;
protected String oAuthToken;
protected String expirationDate;
public V2AccessTokenResponse() {
}
public boolean isSuccess() {
return this.success;
}
public String getOAuthToken() {
return this.oAuthToken;
}
public String getExpirationDate() {
return this.expirationDate;
}
public void setSuccess(final boolean success) {
this.success = success;
}
public void setOAuthToken(final String oAuthToken) {
this.oAuthToken = oAuthToken;
}
public void setExpirationDate(final String expirationDate) {
this.expirationDate = expirationDate;
}
}
Decided to copy-and-paste this time as you might want to copy it somewhere elese
Comment From: cowtowncoder
Ah ok. This now makes more sense: the problem is Lombok's definition starting from Field name, creating what it thinks are properly named getter/setter method. Unfortunately, Jackson does the reverse: it starts with method names, trying to figure out logical property names from those (and leaving field name as is). This leads to discrepancy since getOAuthToken()
becomes either:
oauthToken
(default Jackson setting) orOAuthToken
(bean convention compatible handling)
but it will not (can not) ever become oAuthToken
as there is no information to guide that decision, unless there was some logic to try use case-insensitive heuristic to look for fields with mismatching naming convention (and if so, which name to select as "correct" one?)
To resolve this issue, you will probably need to add @JsonProperty
annotation(s) to indicate desired cased name in cases where you have a single leading lower-case letter in name: this is the main (only?) case that causes problems.
Or perhaps avoid use of such field names due to problems converting field/getter/setter names.
There may be other settings in Lombok that control behavior from that end as well.
On short term I don't think Jackson behavior can really be changed, although since this is not the first time this particular issues has come up, maybe for 3.x it'd be worth thinking of how this could be addressed (possibly something like "field-name driven naming" where matching starts with field names... but that may have other problems of its own).
Comment From: victorgcapone
Bummer, well I will look into Lombok's docs to see if the is any way to fix that, but for now, @JsonProperty works fine, it's a shame that it's not as clean, but it'll have to do, thanks @cowtowncoder
Comment From: cowtowncoder
Yeah it's unfortunate edge case, and tricky to diagnose when you hit it for first time. There is/was one sort of related naming issue with Kotlin's "is-getters", where you have:
boolean isEnabled;
public boolean getEnabled() { return isEnabled; }
generated for data classes, where you only specify boolean isEnabled
; and that also required some creative thinking to get around (there is a hook for AnnotationIntrospector
to allow reconciliation).
So this is an area where future improvements are possible once problems are understood better. But Jackson's internal handling has grown to be somewhat complicated as well, not trivial to change without breaking things.
Comment From: cowtowncoder
Collecting cases for edge case I hope to address, re-opening for now.
Comment From: cowtowncoder
Part of #5152 fix, closing.