Why does Jackson 2 not recognize the first capital letter if the leading camel case word is only a single letter long?
Asked Answered
I

7

71

I'm using Spring 4 MVC with Jackson 2 for my service. For one of the operations I have a request object that has an attribute where the leading camel case word this is only one letter in length:

private String aLogId;

This class has the appropriately named getters and setters:

public String getALogId() { return aLogId; }
public void setALogId(String aLogId) { this.aLogId = aLogId; }

However, when I attempt to post a request to this service using the corresponding JSON property:

{"aLogId":"This is a log id"}

I'm receiving a 500 response from the Spring framework saying the field is not recognized and my controller class is never called:

Could not read JSON: Unrecognized field "aLogId" (class

However, when I change the "L" to lower case, the request is deserialized as expected and my controller class is hit:

{"alogId":"This is a log id"}

Why does Jackson expect the "L" to be lower case when it is obviously the second word in the camel case convention for the attribute and intended to be in upper case? Is it because the first word is only a single letter long?

There are other attributes in the request object where the first word is more than one letter and those attributed don't face this same issue with the mismatch in case.

Irrelevancy answered 13/5, 2015 at 3:10 Comment(2)
that would be an interesting bug. what about a single-letter word in the middle, like logAId - would it fail too?Hawserlaid
This problem goes away if you rename you getter and setter to setaLogId and getaLogId. That flies in the face of convention though. There is definitely something 'buggy' when you have single letter words.Atthia
P
95

The problem you are seeing is due to the fact that Jackson uses Java Bean naming conventions to figure out the the Json properties in a Java class.

Here is a reference of the specific problem you see, the recommendation is not to capitalize either of the first two letters in your field. If you use an IDE like IntelliJ or eclipse and let the IDE generate the setters for you, you will notice the same "behavior" occurs, you will end up with the following methods:

public void setaLogId(String aLogId) {
    this.aLogId = aLogId;
}

public String getaLogId() {
    return aLogId;
}

Hence, when you change the "L" to lower case Jackson was able to figure it out the field you wanted to map.

Having said the above, you still have the alternative to use the "aLogId" field name and make Jackson work all you have to do is use the @JsonProperty annotation with the aLogId in it.

@JsonProperty("aLogId")
private String aLogId;

The following test code is to show how this will work:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

    @JsonProperty("aLogId")
    private String aLogId;

    public void setaLogId(String aLogId) {
        this.aLogId = aLogId;
    }

    public String getaLogId() {
        return aLogId;
    }

    public static void main(String[] args) {

        ObjectMapper objectMapper = new ObjectMapper();

        Test test = new Test();

        test.setaLogId("anId");

        try {
            System.out.println("Serialization test: " + objectMapper.writeValueAsString(test));


            String json = "{\"aLogId\":\"anotherId\"}";

            Test anotherTest = objectMapper.readValue(json, Test.class);

            System.out.println("Deserialization test: " +anotherTest.getaLogId());

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

    }
}

The output of the test is:

Serialization test: {"aLogId":"anId"}

Deserialization test: anotherId

Peduncle answered 13/5, 2015 at 6:36 Comment(3)
This does help. Curious though, in the Java Bean spec 8.8, they actually mention if the first two letter are uppercase they leave it alone, so it would seem Jackson should pick this up as ALogId instead of alogId. So is Jackson deviating from the Java Bean spec then? Either way, this is a great answer and pointed me in the correct direction. Thank you and accepted.Irrelevancy
I am using Jackson 2.8.8and I have the same problem. But when I try your solution (anotating with @JsonProperty("aLogId") the "aLogId" field), I receive two JSON fields: { "aLogId":"anId", "alogId":"anId" } So the field is duplicated, wich is not correct! Anyone in the same situation?Pinfish
Even if we use setters and getters as getALogId()/setALogId, and use @JsonProperty("aLogId") private String aLogId; on logID field, still it works, why?Eads
A
12

@JsonProperty as suggested by the current answer has the disadvantage that you need to repeat it for every single property, and that it's invasive (you need to change the class being mapped).

A more general approach is to provide a custom Property Naming Strategy:

Java:

public class CustomSnakeCase extends PropertyNamingStrategy.PropertyNamingStrategyBase {
    private static final Pattern REGEX = Pattern.compile("[A-Z]");

    @Override
    public String translate(String input) {
        if (input == null)
            return input; // garbage in, garbage out

        if (!input.isEmpty() && Character.isUpperCase(input.charAt(0)))
            input = input.substring(0, 1).toLowerCase() + input.substring(1);

        return REGEX.matcher(input).replaceAll("_$0").toLowerCase();
    }
}

Kotlin:

class CustomSnakeCase : PropertyNamingStrategy.PropertyNamingStrategyBase() {
    private companion object {
        val REGEX = Regex("[A-Z]")
    }

    override fun translate(input: String?) =
        input?.decapitalize()?.replace(REGEX, "_$0")?.toLowerCase()
}

Usage:

new ObjectMapper()
    .setPropertyNamingStrategy(new CustomSnakeCase())
    .enable(MapperFeature.USE_STD_BEAN_NAMING)

Note: The implementation I provide above assumes that input is camelCase (no uppercase beginning). USE_STD_BEAN_NAMING is needed to handle 1-character prefixes such as aField consistely.

The implementation provides the following mapping, you might adjust it according to your needs:

camelCase      snake_case
----------------------------
simple         simple
a              a
sepaRated      sepa_rated
iOException    i_o_exception
xOffset        x_offset
theWWW         the_w_w_w
sepaRated32    sepa_rated32
sepa32Rated    sepa32_rated
Actinium answered 5/11, 2018 at 14:26 Comment(1)
Doesn't work, if you debug with a mapper, translate() is called with the bug ever (xoffset, in your example, not xOffset)Butt
P
10

This has worked for me; @JsonProperty annotation on getters!

import com.fasterxml.jackson.annotation.JsonProperty;

public class PaytmRequestJson {
    private String ORDERID;
    private String MID;
    private String CHECKSUMHASH;

    @JsonProperty("ORDERID")
    public String getORDERID() {
        return ORDERID;
    }

    public void setORDERID(String ORDERID) {
        this.ORDERID = ORDERID;
    }

    @JsonProperty("MID")
    public String getMID() {
        return MID;
    }

    public void setMID(String MID) {
        this.MID = MID;
    }

    @JsonProperty("CHECKSUMHASH")
    public String getCHECKSUMHASH() {
        return CHECKSUMHASH;
    }

    public void setCHECKSUMHASH(String CHECKSUMHASH) {
        this.CHECKSUMHASH = CHECKSUMHASH;
    }
}
Primordium answered 21/9, 2017 at 8:3 Comment(2)
Thanks, that worked for me too. Because we were using Kotlin we needed to annotate the field with annotation use-site targets: data class PaytmRequestJson(@get:JsonProperty("ORDERID") val orderId)Wept
Specifying "annotation on getters" solves for me. I had Data's annotation from Lombok, annotating JsonProperty on private fields resulted in duplicated json properties (I receive {"aProperty":"value"; "aproperty":"value"}). Replaced Data with getters and setters, and annotated only getters; json response is ok nowSiqueiros
L
7

I got the same problem on Kotlin. Resolved by using Use the @JsonProperty annotation to the accessor methods.

For example: @get:JsonProperty("ID") val id: String = ""

Lindsy answered 29/11, 2019 at 7:12 Comment(0)
W
6

My understanding is that Jackson uses by default its own naming convention, which is very close, but not exactly the same, to the Java Bean naming convention. A MapperFeature option, MapperFeature.USE_STD_BEAN_NAMING, was added in Jackson 2.5.0 to tell Jackson to use the Java Bean naming convention -- see Jackson Issue 653. For backward compatibility, the default value for MapperFeature.USE_STD_BEAN_NAMING is false.

Weismann answered 17/4, 2017 at 19:28 Comment(0)
R
0

Annotating the field value with this @JacksonXmlProperty(localName = "MsgType") has been serving me right always

i.e

@JacksonXmlProperty(localName = "MsgType")
private String MsgType;
Ryle answered 22/10, 2021 at 8:29 Comment(0)
S
0

For now, adding @Jacksonized to your class also works if you use Lombok's @Builder/@SuperBuilder.

Selffertilization answered 15/4, 2022 at 17:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.