Jackson 通过注解实现 POJO 序列化与反序列化规则,包含以下功能:
属性命名
属性包含
属性文档、元数据
序列化和反序列化细节
反序列化细节
序列化细节
类型处理
对象引用、标识
元注解
本文展示了如何快速上手 Jackson 内建属性以及如何创建自定义命名策略。
属性命名
`@JsonProperty` 用来表示序列化结果中的属性名称:
`@JsonProperty.value`:使用的名称
`@JsonProperty.index`: 物理索引
`@JsonProperty.defaultValue`: 默认文本,定义为元数据
例如:
```java
import com.fasterxml.jackson.annotation.JsonProperty;
public class BeanToTest {
/*
* 可以为属性名称指定字符串常量
* 这时无论采取何种命名机制,
* 都会覆盖 PropertyNamingStrategy, 只返回 “fieldOne”
*/
@JsonProperty("fieldOne")
private String fieldOne;
@JsonProperty("fieldTwo")
private String fieldTwo;
public String getFieldOne() {
return fieldOne;
}
public void setFieldOne(String fieldOne) {
this.fieldOne = fieldOne;
}
public String getFieldTwo() {
return fieldTwo;
}
public void setFieldTwo(String kFieldTwo) {
this.fieldTwo = kFieldTwo;
}
}
```
@JsonNaming
`@JsonNaming` 注解用来指定属性序列化使用的命名策略,覆盖默认实现。可以通过 `value` 属性指定策略,包括自定义策略。
除了默认的 LOWER_CAMEL_CASE 机制,比如 `lowerCamelCase` 外,Jackson 还提供了四种内置命名策略:
KEBAB_CASE:“Lisp” 风格,采用小写字母、连字符作为分隔符,例如 “lower-case” 或 “first-name”
LOWER_CASE:所有的字母小写,没有分隔符,例如 lowercase
SNAKE_CASE:所有的字母小写,下划线作为名字之间分隔符,例如 snake_case.
UPPER_CAMEL_CASE:所有名字(包括第一个字符)都以大写字母开头,后跟小写字母,没有分隔符,例如 UpperCamelCase
"注意:"上述规则可应用于某个类,也可以作为所有类的全局命名规则。
>>>
`@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)` 用于单个类的注解命名,
`objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE)` 用作全局命名
>>>
下面的示例展示了如何配置两种策略。这里通过单元测试进行演示:
```java
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
@RunWith(JUnit4.class)
public class JacksonBeanNamingKebabTest {
@Test
public void testBeanNames() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
// 为 ObjectMapper 设置全局 PropertyNamingStrategy
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
BeanToTest bt = new BeanToTest();
bt.setFieldOne("field one data.");
bt.setFieldTwo("field two data.");
mapper.writeValue(System.out, bt);
}
// 为单个类设置 PropertyNamingStrategy 应使用 @JsonNaming
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
private class BeanToTest {
private String fieldOne;
private String fieldTwo;
public String getFieldOne() {
return fieldOne;
}
public void setFieldOne(String fieldOne) {
this.fieldOne = fieldOne;
}
public String getFieldTwo() {
return fieldTwo;
}
public void setFieldTwo(String kFieldTwo) {
this.fieldTwo = kFieldTwo;
}
}
}
```
"注意:"如果同时设置了全局规则和某个类的命名规则,后者会覆盖全局设置。
运行上面的测试,输出如下:
```
{
"field_one":"field one data.",
"field_two":"field two data."
}
```
在某些情况下,上述方法可能还不能满足要求,例如使用了其他库或工具生成代码,包含 getter 和 setter。让我们看下面这个例子。
假设类定义如下:
```java
public class BeanToTest {
@JsonProperty("fielOne")
private String fieldOne;
@JsonProperty("kFieldTwo")
private String kFieldTwo;
public String getFieldOne() {
return fieldOne;
}
public void setFieldOne(String fieldOne) {
this.fieldOne = fieldOne;
}
public String getKFieldTwo() {
return kFieldTwo;
}
public void setKFieldTwo(String kFieldTwo) {
this.kFieldTwo = kFieldTwo;
}
}
```
可以注意到,kFieldTwo 的 getter/setter 方法显得有些不同。它们的命名并不遵守 bean 命名规则,即首字母应该大写。运行结果如下:
```
{
"kfieldTwo":"field constant.",
"fielOne":"field one data",
"kFieldTwo":"field constant."
}
```
可以看到,结果引入了一个多余字段 `kfieldTwo`。Jackson 支持增加自定义属性命名策略,这时可以派上用场。
```java
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
@RunWith(JUnit4.class)
public class JacksonBeanNamingCustomTest {
@Test
public void testBeanNames() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
@Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return field.getName();
}
@Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return convert(method, defaultName);
}
@Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return convert(method, defaultName);
}
/**
* 获取 getter/setter 方法所在类的名称
*
* @param method
* @param defaultName
* - jackson 生成的名字
* @return 正确的属性名
*/
private String convert(AnnotatedMethod method, String defaultName) {
Class<?> clazz = method.getDeclaringClass();
List<Field> flds = getAllFields(clazz);
for (Field fld : flds) {
if (fld.getName().equalsIgnoreCase(defaultName)) {
return fld.getName();
}
}
return defaultName;
}
/**
* 获取类中所有字段名
*
* @param currentClass
* - 不允许为 null
* @return 当前类与父类中所有字段
*/
private List<Field> getAllFields(Class<?> currentClass) {
List<Field> flds = new ArrayList<>();
while (currentClass != null) {
Field[] fields = currentClass.getDeclaredFields();
Collections.addAll(flds, fields);
if (currentClass.getSuperclass() == null)
break;
currentClass = currentClass.getSuperclass();
}
return flds;
}
});
BeanToTest bt = new BeanToTest();
bt.setFieldOne("field one data");
bt.setKFieldTwo("field constant.");
mapper.writeValue(System.out, bt);
}
private class BeanToTest {
@JsonProperty("fielOne")
private String fieldOne;
@JsonProperty("kFieldTwo")
private String kFieldTwo;
public String getFieldOne() {
return fieldOne;
}
public void setFieldOne(String fieldOne) {
this.fieldOne = fieldOne;
}
public String getKFieldTwo() {
return kFieldTwo;
}
public void setKFieldTwo(String kFieldTwo) {
this.kFieldTwo = kFieldTwo;
}
}
}
```
输出:
```
{
"kFieldTwo":"field constant.",
"fielOne":"field one data"
}
```
从上面结果可以看出,我们可以根据自己的需求修改默认命名策略。
也可以为 `PropertyNamingStrategy` 新建文件,使用 `@JsonNaming(…)` 对单个类应用自定义规则。