Does Jackson deserialise the second character to lowercase for a property
Asked Answered
P

3

6

We've defined a model in our service code as -

@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class SomeData {

    public boolean tnAvailable;

    @NonNull
    public String sTempChange;

    public boolean isTnAvailable() {
       return faAvailable;
    }

    public void setTnAvailable(boolean faAvailable) {
        this.faAvailable = faAvailable;
    }

    @Nonnull
    public String getSTempChange() {
        return sTempChange;
    }

    public void setSTempChange(@Nonnull String sTempChange) {
        this.sTempChange = sTempChange;
    }

}

When the api including the above model in response is queried , we get the response as -

"someData": {
    "tnAvailable": true,
    "stempChange": "trial_001"
}

What surprised us was the stempChange(notice lowercase t) instead of sTempChange in the attributes of the response.

Suspecting the cause to be Jackson com.fasterxml.jackson.core:jackson-core:2.5.2 while serializing and deserializing of the objects during API calls since we do not alter the attribute using any other getter-setter ot wrapper. Why would this so happen and is serialization/deserialization the correct direction to look for this?

Edit - From the comment by @Windle, trying to explain what's different here. I re-iterate "The question though there relates pretty much to the same situation. Yet I 'm looking forward to the reason's for such implementation and documentation in fasterxml as well."

Peterman answered 31/3, 2017 at 16:7 Comment(3)
Possible duplicate of Why does Jackson 2 not recognize the first capital letter if the leading camel case word is only a single letter long? - that one has a nice answer though =)Pileus
@Pileus The question though there relates pretty much to the same situation. Yet I 'm looking forward to the reason's for such implementation and documentation in fasterxml as well.Peterman
The accepted answer in the linked question describes the reason in the very first sentence.Dingman
E
5

Handling of multiple leading capital letters in getters/setters (like "getURL()", or "getFName()"). By default, Jackson will simply lower-case ALL leading upper-case letters, giving "url" and "fname". But if you enable MapperFeature.USE_STD_BEAN_NAMING (added in Jackson 2.5), it will follow what Java Bean naming convention would do, which is only lower-case a single upper-case leading letter; if multiple found, do nothing. That would result in properties "URL" and "FName".

Excruciate answered 16/8, 2017 at 20:14 Comment(4)
will simply lower-case ALL leading upper-case letters... the question is about sTempChange single leading lower case character followed by an upper case character.Peterman
From the question itself, What surprised us was the stempChange(notice lowercase t) instead of sTempChange in the attributes of the responsePeterman
@nullpointer It goes by your getter/setter. Which has been declared as getSTempChangeExcruciate
How is this an accepted answer?Karlkarla
B
3

Yeah it looks like it's getting confused on the method name. You can force the serialized name with the @JsonGetter annotation

@JsonGetter("sTempChange")
public String getSTempChange() {
    return sTempChange;
}
Belsen answered 31/3, 2017 at 16:23 Comment(1)
I know I can force it. More keen to know what confuses it.Peterman
F
3

When I first tried out your SomeData class and serialized it I got the following results:

{"tnAvailable":true,"sTempChange":"trial_000","stempChange":"trial_000"}

This means that jackson doesn't match your getters/setters with the sTempChange property and they are treated as different properties. After adding the following configuration for my mapper I was able to reproduce your case:

    ObjectMapper objectMapper = new ObjectMapper();

    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
    objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
    objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.ANY);
    objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.ANY);

Now the reason for your error is because Jackson uses its own implementation of bean utilities (com.fasterxml.jackson.databind.util.BeanUtil) which is used when a class is processed for fields, getters and setters (done by com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector) when an instance is serialized/deserialized. Methods of interests are okNameForGetter and okNameForSetter. In those methods there are 2 other methods used depending on the MapperFeature.USE_STD_BEAN_NAMING (it is passed in the stdNaming argument in all methods). The two methods are used in the following manner:

return stdNaming
                ? stdManglePropertyName(name, prefix.length())
                : legacyManglePropertyName(name, prefix.length());

The stdManglePropertyName follows the Java Beans specification in section 8.8 and the legacyManglePropertyName does not and is used in versions prior to 2.5 of Jackson.

Now after running your getter and setter method names through this methods, however you set MapperFeature.USE_STD_BEAN_NAMING, your getter/setter for sTempChange property is wrongly named. It should be getsTempChange (lowercase 's') and getsTempChange (again lowercase 's') to correctly serialize and deserialize the instances of SomeData class.

Finally here is some code for testing:

import com.fasterxml.jackson.databind.ObjectMapper;


public class Test {

static class SomeData {

    public boolean tnAvailable;

    public String sTempChange;

    public String getsTempChange() {
        return sTempChange;
    }

    public void setsTempChange(String sTempChange) {
        this.sTempChange = sTempChange;
    }

    public boolean isTnAvailable() {
        return tnAvailable;
    }

    public void setTnAvailable(boolean tnAvailable) {
        this.tnAvailable = tnAvailable;
    }

}

public static void main(String[] args) {

    ObjectMapper objectMapper = new ObjectMapper();

//  objectMapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true);

    SomeData someData = new SomeData();
    someData.setsTempChange("trial_000");
    someData.setTnAvailable(true);

//  objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
//  objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
//  objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.ANY);
//  objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.ANY);

    try {
        System.out.println("Serialize: " + objectMapper.writeValueAsString(someData));

        String json = "{ \"tnAvailable\": false, \"sTempChange\": \"trial_001\" }";

        SomeData anotherData = objectMapper.readValue(json, SomeData.class);

        System.out.println("Deserialize: " + anotherData.isTnAvailable() + ", " + anotherData.getsTempChange());

    } catch (Exception e) {
        e.printStackTrace();
    }

}

}

Francoisefrancolin answered 23/8, 2017 at 0:50 Comment(1)
so what to do ?Bagging

© 2022 - 2024 — McMap. All rights reserved.