为什么 Jackson 2 不能识别第一个大写字母,如果前导的驼峰词只有一个字母长?

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

我将 Spring 4 MVC 与 Jackson 2 一起用于我的服务。对于其中一个操作,我有一个请求对象,该对象具有一个属性,其中前导驼峰式单词的长度仅为一个字母:

private String aLogId;

此 class 具有适当命名的 getter 和 setter:

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

但是,当我尝试 post 使用相应的 JSON 属性 请求此服务时:

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

我收到来自 Spring 框架的 500 响应,说该字段未被识别并且我的控制器 class 从未被调用:

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

但是,当我将 "L" 更改为小写时,请求会按预期进行反序列化并且我的控制器 class 被命中:

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

为什么 Jackson 期望 "L" 是小写的,而它显然是属性的驼峰式大小写约定中的第二个词并且打算是大写的?是不是因为第一个字只有一个字母长?

请求对象中还有其他属性,其中第一个单词超过一个字母,并且那些属性不会遇到同样的问题,以防大小写不匹配。

您看到的问题是由于 Jackson 使用 Java Bean 命名约定来确定 Java class 中的 Json 属性。

这是您看到的具体问题的reference,建议不要将您字段中的前两个字母中的任何一个大写。如果您使用像 IntelliJ 或 eclipse 这样的 IDE 并让 IDE 为您生成 setter,您会注意到发生相同的“行为”,您将得到以下方法:

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

public String getaLogId() {
    return aLogId;
}

因此,当您将“L”更改为小写时,Jackson 能够计算出您想要映射的字段。

综上所述,您仍然可以选择使用“aLogId”字段名称并使 Jackson 工作,您所要做的就是使用带有 aLogId@JsonProperty 注释.

@JsonProperty("aLogId")
private String aLogId;

下面的测试代码展示了它是如何工作的:

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();
        }

    }
}

测试的输出是:

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

Deserialization test: anotherId

我的理解是 Jackson 默认使用自己的命名约定,这与 Java Bean 命名约定非常接近,但不完全相同。 Jackson 2.5.0 中添加了一个 MapperFeature 选项 MapperFeature.USE_STD_BEAN_NAMING,以告知 Jackson 使用 Java Bean 命名约定——参见 Jackson Issue 653。为了向后兼容,MapperFeature.USE_STD_BEAN_NAMING 的默认值为 false。

这对我有用; @JsonProperty 吸气剂注解!

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;
    }
}
当前答案所建议的

@JsonProperty 的缺点是您需要为每个 属性 重复它,并且它具有侵入性(您需要更改 class映射)。

更通用的方法是提供自定义 属性 命名策略:

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("_[=10=]").toLowerCase();
    }
}

科特林:

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

    override fun translate(input: String?) =
        input?.decapitalize()?.replace(REGEX, "_[=11=]")?.toLowerCase()
}

用法:

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

注: 我在上面提供的实现假设输入是 camelCase(没有大写开头)。 USE_STD_BEAN_NAMING 需要一致地处理 1 个字符的前缀,例如 aField

实现提供了如下映射,您可以根据需要进行调整:

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

我在 Kotlin 上遇到了同样的问题。通过使用解决 对访问器方法使用 @JsonProperty 注释。

例如:@get:JsonProperty("ID") val id: String = ""

用这个 @JacksonXmlProperty(localName = "MsgType") 注释字段值一直对我有用

@JacksonXmlProperty(localName = "MsgType")
private String MsgType;

现在,如果您使用 Lombok 的 @Builder/@SuperBuilder,将 @Jacksonized 添加到您的 class 也可以。