Jackson的奇技淫巧

doMore 68 2025-03-31

技巧一

如何使用Jackson处理Java中多态类型的序列化与反序列化问题。

通过 @JsonTypeInfo 注解和全局 DefaultTyping 机制确保多态类型的正确转换。

在将多态类型进行JSON序列化后,Jackson无法在反序列化期间找出正确的类型。

@JsonTypeInfo

在类上使用@JsonTypeInfo

// 配置指定使用的类名称(use = JsonTypeInfo.Id.CLASS),并将其作为JSON属性保留(include = JsonTypeInfo.As.PROPERTY)。 属性名称指定为’className’
@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS, 
    include = JsonTypeInfo.As.PROPERTY, 
    property = "className"
)
public class Shape {
}

在属性上使用@JsonTypeInfo


@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Containers {
    @NonNull
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    private List<Shape> shapes;
}

注意:如果该注解同时作用在类和属性上,则以使用在属性上的注解为准,因为它被认为更具体。需要特别说明的是, 当@JsonTypeInfo在属性(字段,方法)上使用时,此注解适用于值。 当在集合类型(List,Map,Array)上使用时,它将应用于元素,而不是集合本身。 对于非集合类型,没有区别。

DefaultTyping

  • JAVA_LANG_OBJECT: 当对象属性类型为Object时生效
  • OBJECT_AND_NON_CONCRETE: 当对象属性类型为Object或者非具体类型(抽象类和接口)时生效
  • NON_CONCRETE_AND+_ARRAYS: 同上, 另外所有的数组元素的类型都是非具体类型或者对象类型
  • NON_FINAL: 对所有非final类型或者非final类型元素的数组

开启DefaultTyping,并且让所有的非final类型对象持久化时都存储类型信息显然能准确的反序列多态类型的数据。

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance, 
    ObjectMapper.DefaultTyping.NON_FINAL, 
    JsonTypeInfo.As.PROPERTY
);

技巧二

对象是怎样被序列化的?序列化的时候字段是怎样的优先级?

对象的属性被初次确定的过程称为自动检测:所有的成员方法和字段被查找。

  • “Getter”方法:所有public,带返回值,符合“getXxx”(“isXxx”,如果返回boolean,被称为“isgetter”)
  • field属性:所有public成员字段被推测要显示的属性,使用字段名字来序列化。

在相同的逻辑属性中同时存在getter和field的情况下。getter方法优先被使用(field被忽略)。下面介绍一下可以改变自动检测的工具。

com.fasterxml.jackson.annotation.JsonAutoDetect

如果默认的自动检测(field和成员方法必须public)不是你想要的,可以下面的方法很容易的改变。

  • @JsonAutoDetect 注解定义在class上;属性”fieldvisibility”,“gettervisibility”,“isgettervisibility”定义

  • ObjectMapper.setVisibilityChecker()可以被使用自定义最小化可见检测


public class JacksonTest {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
//        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
//        mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
//        mapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE);
        Car car = new Car();
        car.setName("奔驰");
        car.setImg("123456.jpg");
        String s = mapper.writeValueAsString(car);
        System.out.println(s);
    }
//    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
//            getterVisibility = JsonAutoDetect.Visibility.NONE,
//            isGetterVisibility = JsonAutoDetect.Visibility.NONE)
    @Data
    public static class Car {
        private String name;
        private String img;
        public String getTest() {
            return "固定字段";
        }
        public Boolean isTestBoolean() {
            return true;
        }
    }
}

// 上述代码输出内容如下:
// {"name":"奔驰","img":"123456.jpg","testBoolean":true,"test":"固定字段"}

// 放开代码中注释输出如下:
// {"name":"奔驰","img":"123456.jpg"}

注意:上述两种方式,使用其中一种就可以,objectMapper 是一种全局的方式

技巧三

明确的忽视属性

首先设置可能存在的自动检测属性,再通过每个属性注解来进一步修改序列化。

  • @JsonProperty (@JsonGetter, @JsonAnyGetter)
  • @JsonIgnore
  • 类注解 @JsonIgnoreProperties 被用于例举属性的逻辑名并不被序列化;
  @JsonIgnoreProperties({ "internal" })
  public class Bean {
    public Settings getInternal() { ... } // ignored
    @JsonIgnore 
    public Settinger getBogus(); // likewise ignored
    public String getName(); // but this would be serialized
  }

技巧四

定义profiles用于动态过滤,使用 com.fasterxml.jackson.annotation.JsonView


public class JacksonTest {


    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
//        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
//        mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
//        mapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE);


        Car car = new Car();
        car.setName("奔驰");
        car.setImg("123456.jpg");
        car.setColor("#f40");
        car.setBackgroundColor("#000000");
        // springmvc/springboot如果标示为RestController,实际上是在后台了配置MappingJackson2HttpMessageConverter转换器,直接使用就好
        // 使用接口来声明多个视图
        // 在pojo的get方法上指定视图
        // 在Controller方法上指定视图
        String s = mapper.writerWithView(BackgroundColorView.class).writeValueAsString(car);
        System.out.println(s);
    }

    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
            getterVisibility = JsonAutoDetect.Visibility.NONE,
            isGetterVisibility = JsonAutoDetect.Visibility.NONE)
    @Data
    public static class Car {

        private String name;

        private String img;

        @JsonView(ColorView.class)
        private String color;

        @JsonView(BackgroundColorView.class)
        private String backgroundColor;

        public String getTest() {
            return "固定字段";
        }

        public Boolean isTestBoolean() {
            return true;
        }

    }


    public interface ColorView {
    }

    public interface BackgroundColorView extends ColorView {
    }