您的位置:首页 > 其它

Jackson 属性自定义命名策略

何曾相识 2021-01-13 20:42 72 查看 https://blog.51cto.com/1508239

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



标签: