为什么杰克逊2不承认第一个大写字母,如果领先的骆驼案例字只有一个字母长?

Haz*_*zok 25 java spring json spring-mvc jackson

我正在使用Spring 4 MVC和Jackson 2进行我的服务.对于其中一个操作,我有一个请求对象,该对象具有一个属性,其中前导驼峰字的长度只有一个字母:

private String aLogId;
Run Code Online (Sandbox Code Playgroud)

这个类有适当命名的getter和setter:

public String getALogId() { return aLogId; }
public void setALogId(String aLogId) { this.aLogId = aLogId; }
Run Code Online (Sandbox Code Playgroud)

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

{"aLogId":"This is a log id"}
Run Code Online (Sandbox Code Playgroud)

我收到了来自Spring框架的500响应,说该字段无法识别,我的控制器类永远不会被调用:

无法读取JSON:无法识别的字段"aLogId"(类

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

{"alogId":"This is a log id"}
Run Code Online (Sandbox Code Playgroud)

为什么杰克逊期望"L"是小写的,因为它显然是该属性的驼峰案例约定中的第二个单词并且意图是大写的?是因为第一个单词只有一个字母长吗?

请求对象中还有其他属性,其中第一个单词不止一个字母,并且这些属性不会遇到与此情况不匹配相同的问题.

jba*_*eta 35

您看到的问题是由于Jackson使用Java Bean命名约定来计算Java类中的Json属性.

下面是一个参考,你看到的具体问题,建议不大写没有在你的领域的前两个字母.如果您使用像IntelliJ或eclipse这样的IDE并让IDE为您生成setter,您会发现同样的"行为"发生,您将最终得到以下方法:

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

public String getaLogId() {
    return aLogId;
}
Run Code Online (Sandbox Code Playgroud)

因此,当您将"L"更改为小写时,杰克逊能够将其想象出您要映射的字段.

说到上面的内容,你仍然可以选择使用"aLogId"字段名称,让杰克逊工作所有你需要做的就是使用其中的@JsonProperty注释aLogId.

@JsonProperty("aLogId")
private String aLogId;
Run Code Online (Sandbox Code Playgroud)

以下测试代码用于说明这将如何工作:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

测试的输出是:

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

Deserialization test: anotherId

  • 这确实有帮助。奇怪的是,在 Java Bean 规范 8.8 中,他们实际上提到了前两个字母是否是大写的,他们不理会它,所以看起来 Jackson 应该把它当作 ALogId 而不是 alogId。那么 Jackson 是否偏离了 Java Bean 规范?无论哪种方式,这是一个很好的答案,并为我指明了正确的方向。谢谢并接受。 (3认同)

The*_*tor 11

@JsonProperty 正如当前答案所建议的那样,缺点是您需要为每个属性重复它,并且它是侵入性的(您需要更改被映射的类)。

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

爪哇

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

科特林:

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

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

用法:

new ObjectMapper()
    .setPropertyNamingStrategy(new CustomSnakeCase())
    .enable(MapperFeature.USE_STD_BEAN_NAMING)
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)


小智 6

这对我有用;@JsonProperty 上的 getter 注释!

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Geo*_*der 5

我的理解是 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。


小智 5

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

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