Skip to content

Jackson 2-使用注解进行json序列化与反序列化

在本篇文章中,我们主要介绍Jackson中提供的相关注解,以帮助我们更好地进行json序列化与反序列化。

参考文章链接:https://www.baeldung.com/jackson-annotations

0. 准备工作

首先准备一个商品类Goods

java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {
    private int id;
    private String goodsCode;
    private String goodsName;
    private BigDecimal price;
    private String spec;
    private String unit;
    private String remark;
}

1. 序列化注解

注意,以下代码示例均

1.1 @JsonIgnore

@JsonIgnore标注在字段上,标识该字段不进行序列化。

java
// 在remark字段上标注 @JsonIgnore
@JsonIgnore
private String remark;
java
@Test
void test01() throws JsonProcessingException {
    Goods goods = new Goods(1, "A001", "矿泉水", new BigDecimal(2), "550ml", "瓶", "饮用天然矿泉水");

    ObjectMapper objectMapper = new ObjectMapper();
    String jsonString = objectMapper.writeValueAsString(goods);
    System.out.println(jsonString);
}

结果json字符串中不包含remark字段:

txt
{"id":1,"goodsCode":"A001","goodsName":"矿泉水","price":2,"spec":"550ml","unit":"瓶"}

1.2 @JsonIgnoreProperties

@JsonIgnoreProperties标识类中不进行序列化的字段。

java
@JsonIgnoreProperties(value = {"id","remark"})  // 表示id 和 remark 字段不进行序列化
public class Goods {
  ...
}

测试代码复用,此处省略。测试结果如下:

txt
{"goodsCode":"A001","goodsName":"矿泉水","price":2,"spec":"550ml","unit":"瓶"}

1.3 @JsonIncludeProperties

@JsonIncludeProperties标识类中需要进行序列化的字段,其余没包括的字段不进行序列化。

java
@JsonIncludeProperties(value = {"goodsCode", "goodsName"})
public class Goods {
  ...
}

测试代码复用,此处省略。测试结果如下:

txt
{"goodsCode":"A001","goodsName":"矿泉水"}

1.4 @JsonInclude

@JsonInclude可用于筛选出需要进行序列化的字段。

1.4.1 基础用法

例如,值null的字段不进行序列化。首先,将Goodsremark置为null

java
Goods goods = new Goods(1, "A001", "矿泉水", new BigDecimal(2), "550ml", "瓶", null);

然后在Goods类上进行标注:

java
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class Goods {
  ...
}

结果如下:

txt
{"id":1,"goodsCode":"A001","goodsName":"矿泉水","price":2,"spec":"550ml","unit":"瓶"}

1.4.2 自定义过滤器

@JsonInclude 注解中的value可设定值为JsonInclude.Include.CUSTOM,然后通过valueFilter自定义过滤器。自定义过滤器需实现 boolean equals(Object o)方法,当返回值为true时,表示该值不序列化,反之则进行序列化。例如:

java
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = GoodsFilter.class)
public class Goods {
  ...
}

class GoodsFilter {
    public boolean equals(Object o) {
        if(o instanceof BigDecimal){
            BigDecimal bigDecimal = (BigDecimal) o;
            return bigDecimal.compareTo(BigDecimal.ZERO) <= 0;
        }
        return false;
    }
}

上述自定义过滤器表示,如果价格小于等于零,则价格不序列化。

java
Goods goods = new Goods(1, "A001", "矿泉水", new BigDecimal(-2), "550ml", "瓶", null);
txt
{"id":1,"goodsCode":"A001","goodsName":"矿泉水","spec":"550ml","unit":"瓶","remark":null}

1.5 @JsonPropertyOrder

@JsonPropertyOrder可用于指定序列化后字段的顺序。

java
@JsonPropertyOrder({"goodsCode", "goodsName", "unit", "price", "spec", "remark"}) 
public class Goods {
  ...
}
txt
{"goodsCode":"A001","goodsName":"矿泉水","unit":"瓶","price":2,"spec":"550ml","remark":"天然饮用矿泉水","id":1}

未指定的字段顺序,将按照定义顺序。

我们也可以通过@JsonPropertyOrder(alphabetic = true)将字段顺序指定为字母表顺序。

1.6 @JsonSerialize

@JsonSerialize可用于指定自定义序列化器。

首先创建自己的序列化器:

java
class GoodsSerializer extends StdSerializer<Goods>{

    public GoodsSerializer() {
        super(Goods.class);
    }

    public GoodsSerializer(Class<Goods> t) {
        super(t);
    }

    @Override
    public void serialize(Goods value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();

        gen.writeStringField("商品编码", value.getGoodsCode());
        gen.writeStringField("商品名称", value.getGoodsName());
        gen.writeStringField("价格", value.getPrice().toPlainString());
        gen.writeStringField("规格", value.getSpec());
        gen.writeStringField("单位", value.getUnit());
        gen.writeStringField("备注", value.getRemark());

        gen.writeEndObject();
    }
}

然后通过注解使用:

java
@JsonSerialize(using = GoodsSerializer.class)
public class Goods {
  ...
}

结果如下:

txt
{"商品编码":"A001","商品名称":"矿泉水","价格":"2","规格":"550ml","单位":"瓶","备注":"天然饮用矿泉水"}

2. 反序列化注解

2.1 @JsonAlias

@JsonAlias可用于在反序列化过程中,为字段提供别名。

java
@JsonAlias({"商品编码"})
private String goodsCode;
@JsonAlias({"商品名称"})
private String goodsName;
java
@Test
void test03() throws JsonProcessingException {
    String jsonString = "{\"商品编码\":\"A001\",\"商品名称\":\"矿泉水\",\"价格\":\"2\",\"规格\":\"550ml\",\"单位\":\"\",\"备注\":\"天然饮用矿泉水\"}";

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    Goods goods = objectMapper.readValue(jsonString, Goods.class);
    System.out.println(goods);
}
txt
Goods(id=0, goodsCode=A001, goodsName=矿泉水, price=null, spec=null, unit=null, remark=null)

2.2@JsonDeserialize

@JsonDeserialize用于指定自定义反序列化器。

首先定义自己的反序列化器:

java
class GoodsDeserializer extends StdDeserializer<Goods> {

    public GoodsDeserializer() {
        super(Goods.class);
    }
    public GoodsDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Goods deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        TreeNode treeNode = p.readValueAsTree();

        String goodsCode = ((TextNode)treeNode.get("商品编码")).asText();
        String goodsName = ((TextNode)treeNode.get("商品名称")).asText();
        String spec = ((TextNode)treeNode.get("规格")).asText();
        String unit = ((TextNode)treeNode.get("单位")).asText();
        String price = ((TextNode)treeNode.get("价格")).asText();
        String remark = ((TextNode)treeNode.get("备注")).asText();

        return new Goods(0, goodsCode, goodsName, new BigDecimal(price), spec, unit, remark);
    }
}

然后使用自定义的反序列化器:

java
@JsonDeserialize(using = GoodsDeserializer.class)
public class Goods {
  ...
}
java
@Test
void test03() throws JsonProcessingException {
    String jsonString = "{\"商品编码\":\"A001\",\"商品名称\":\"矿泉水\",\"价格\":\"2\",\"规格\":\"550ml\",\"单位\":\"\",\"备注\":\"天然饮用矿泉水\"}";

    ObjectMapper objectMapper = new ObjectMapper();

    Goods goods = objectMapper.readValue(jsonString, Goods.class);
    System.out.println(goods);
}

3. 通用注解

3.1 @JsonProperty

@JsonProperty可用于修改json中的字段名称。

java
@JsonProperty("商品名称")
private String goodsName;
txt
{"id":1,"goodsCode":"A001","price":2,"spec":"550ml","unit":"瓶","remark":"天然饮用矿泉水","商品名称":"矿泉水"}

3.2 @JsonRootName

@JsonRootName用于在 JSON 序列化和反序列化过程中添加或移除根元素。简单来说,它会在生成的 JSON 字符串的最外层包裹一个以指定名称命名的对象。如果不指定名称,则默认名称是类名。

TIP

注意,如果要使该注解生效,则需要配置ObjectMapper对象。开启序列化配置SerializationFeature.WRAP_ROOT_VALUE和反序列化配置DeserializationFeature.UNWRAP_ROOT_VALUE

java
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
java
@JsonRootName(value = "goods")
public class Goods {
  ...
}
java
@Test
void test01() throws JsonProcessingException {
    Goods goods = new Goods(1, "A001", "矿泉水", new BigDecimal(2), "550ml", "瓶", "天然饮用矿泉水");

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
    objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
    String jsonString = objectMapper.writeValueAsString(goods);
    System.out.println(jsonString);

    String anotherJsonString = "{\"goods\":{\"id\":1,\"goodsCode\":\"A001\",\"goodsName\":\"矿泉水\",\"price\":2,\"spec\":\"550ml\",\"unit\":\"\",\"remark\":\"天然饮用矿泉水\"}}";
    Goods goods1 = objectMapper.readValue(anotherJsonString, Goods.class);
    System.out.println(goods1);
}

3.3 @JsonFormat

@JsonFormat用于格式化日期时间类型,可用于Date类和Java 8 日期时间类型,例如LocalDateTime

java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Event {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date date;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime localDateTime;
}
java
@Test
void test04() throws JsonProcessingException {
    Event event = new Event(new Date(), LocalDateTime.now());

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());

    String jsonString = objectMapper.writeValueAsString(event);
    System.out.println(jsonString);
}
txt
{"date":"2024-12-15 09:12:07","localDateTime":"2024-12-15 17:12:07"}

4. 多态注解

  • @JsonTypeInfo:类型信息注解,用于父类。属性如下:
    • use:指定使用哪种方式包含类型信息。常用的值有:
      • JsonTypeInfo.Id.CLASS:使用全限定类名;
      • JsonTypeInfo.Id.NAME:使用逻辑名称,需配合 @JsonSubTypes 使用;
    • include:指定类型信息包含在哪里。常用的值有:
      • JsonTypeInfo.As.PROPERTY:类型信息作为属性包含在对象中(常用)
      • JsonTypeInfo.As.WRAPPER_OBJECT:类型信息作为键,对象作为值;
    • property:指定类型信息的名称,默认为全限定类名;
  • @JsonSubTypes:子类型注册,用于父类。属性是@JsonSubTypes.Type数组,@JsonSubTypes.Type的属性如下:
    • value:子类型
    • name:子类型的逻辑名称。

举例如下:

java
public interface Animal {
    void makeSound();
}
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dog implements Animal {

    private String color;

    @Override
    public void makeSound() {
        System.out.println("汪");
    }
}
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cat implements Animal {

    private String weight;

    @Override
    public void makeSound() {
        System.out.println("喵");
    }
}

然后在父类中增加类型注解:

java
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public interface Animal {
    void makeSound();
}

测试代码如下:

java
@Test
void test01() throws JsonProcessingException {
    Animal animal = new Dog("白");

    ObjectMapper objectMapper = new ObjectMapper();

    String jsonString = objectMapper.writeValueAsString(animal);
    System.out.println(jsonString);

    String anotherJsonString = "{\"type\":\"dog\",\"color\":\"\"}";
    Animal animal1 = objectMapper.readValue(anotherJsonString, Animal.class);
    System.out.println(animal1);
  	animal1.makeSound();
}

结果如下:

txt
{"type":"dog","color":"白"}
Dog(color=黑)