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

新手上路,请多包涵

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

 private String aLogId;

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

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

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

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

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

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

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

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

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

请求对象中还有其他属性,其中第一个单词不止一个字母,并且那些属性不会面临与大小写不匹配相同的问题。

原文由 Hazok 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

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

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

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

public String getaLogId() {
    return aLogId;
}

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

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

 @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

原文由 jbarrueta 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

科特林:

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

    override fun translate(input: String?) =
        input?.decapitalize()?.replace(REGEX, "_$0")?.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

原文由 TheOperator 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题