查看原文
其他

Jackson 属性自定义命名策略

ImportNew ImportNew 2021-12-02

(给ImportNew加星标,提高Java技能)


编译:ImportNew/唐尤华

dzone.com/articles/jackson-property-custom-naming-strategy


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(…)` 对单个类应用自定义规则。


推荐阅读

(点击标题可跳转阅读)

Jackson 通过自定义注解来控制 json key 的格式

Spring Async 最佳实践(3):完结篇

Java 新建对象过程分析


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

好文章,我在看❤️

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存