首頁 > 軟體

抽象類使用Jackson序列化問題

2023-11-01 10:01:10

抽象類使用Jackson序列化

當java物件中含List<Object>時,如果Object一個抽象類或介面,這裡就會出現java多型的現象,比如List<Animal>, 如果Animal是個抽象類,並且有多個子類時,由於List中儲存的Animal沒有明確指向具體的子類或實現類,json反序列化java物件時就會丟擲提示:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException:Can not construct instance of Animal, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

可以使用@JsonTypeInfo與@JsonSubTypes來解決此類問題,通過註解,可以在序列化時,儲存具體的型別資訊到json中,當json反序列到java物件時,就可以根據具體型別資訊建立正確的java物件。

  • @JsonTypeInfo – indicates details of what type information to include in serialization 指出序列化包含的型別資訊細節
  • @JsonSubTypes – indicates sub-types of the annotated type 指出被註解型別的子類
  • @JsonTypeName – defines a logical type name to use for annotated class 定義被註解類使用的邏輯名稱
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public class Animal {
    public String name;
 
    public Animal(String name) {
    }
}
 
@JsonTypeName("dog")
// 這裡在子類中指定的type name必須和抽象類中註解@JsonSubTypes中name屬性指定的值保持一致
public class Dog extends Animal {
    public double barkVolume;
 
    public Dog(String name) {
        super(name);
        barkVolume = 0.5;
    }
}
 
@JsonTypeName("cat")
public class Cat extends Animal {
    boolean likesCream;
    public int lives;
 
    public Cat(String name) {
        super(name);
        likesCream = true;
        lives = 10;
    }
}
  
    @Test
    public void whenSerializingPolymorphic_thenCorrect()
            throws JsonProcessingException {
        Zoo.Dog dog = new Zoo.Dog("lacy");
        Zoo zoo = new Zoo(dog);
 
        String result = new ObjectMapper()
                .writeValueAsString(zoo);
 
        assertThat(result, containsString("type"));
        assertThat(result, containsString("dog"));
    }
 
    序列化zoo物件,結果如下:
        {
         "type":"dog",
         "name":"lacy",
         "barkVolume":0
        }
 
    @Test
    public void whenDeserializingPolymorphic_thenCorrect()
            throws IOException {
        String json = "{"name":"lacy","type":"cat"}";
 
        Animal animal =
                new ObjectMapper().readerFor(Animal.class).readValue(json);
 
        assertEquals("lacy", animal.name);
        assertEquals(Cat.class, animal.getClass());
    }

記一次jackson序列化Boolean的坑

@Data
public class CouponTemplateDto {
    /**
     * 優惠券型別id
     */
    private Long couponTypeId;
    /**
     * 優惠券模板id
     */
    private Long couponTemplateId;
    /**
     * 使用者id
     */
    private Long userId;

    /**
     * 優惠券描述
     */
    private String description;
    /**
     * 面值,滿200減30,則此值為30
     */
    private BigDecimal value;
    /**
     * 從次日起,多少天可用
     */
    private Integer delayDays;
    /**
     * 從當日起,多少天可用
     */
    private Integer nowDays;
    /**
     * 滿多少可以減,滿200減30,則此值為200
     */
    private BigDecimal fullAmount;

    /**
     * 券號
     */
    private String couponNo;

    /**
     * 有效起始日期
     */
    private Date startTime;

    /**
     * 失效日期
     */
    private Date endTime;

    /**
     * 建立時間
     */
    private Date createTime;

    /**
     * 使用日期
     */
    private Date useTime;

    /**
     * 券使用狀態:0-未使用 1-已使用 2-已過期
     */
    private Integer couponUseStatus;
    /**
     * 過期前多少天提醒,預設7天
     */
    private Integer overDueRemind;
    /**
     * 優惠券標題
     */
    private String title;

    /**
     * 優惠券是否能開始使用
     */
//    @JsonProperty("isStart")
    private Boolean start;
    /**
     * 優惠券是否過期
     */
//    @JsonProperty("isEnd")
    private Boolean end;

    private Boolean getStart() {
        return startTime.before(new Date());
    }

    private Boolean getEnd() {
        return endTime.before(new Date());
    }
}

我定義了一個這樣的類,我們專案用的是Spring Boot,預設底層採用的是jackson序列化,但是在使用中出了一個問題private Boolean start跟private Boolean end這兩個欄位一直無法序列化

總結排查思路如下

1.是boolean還是Boolean,到底是基本資料型別還是包裝類,如果是基本資料型別的話(包裝類可以使用,但是不推薦),不要使用is開頭。我們可以看看阿里巴巴規範中的這段話

【強制】POJO類中的任何布林型別的變數,都不要加 is,否則部分框架解析會引起序列化錯誤。

反例:定義為基本資料型別 boolean isSuccess;的屬性,它的方法也是 isSuccess(),RPC框架在反向解析的時候,“以為”對應的屬性名稱是 success,導致屬性獲取不到,進而丟擲異常。

2.這個錯誤也是我犯的錯誤,我複寫了get方法,方法的存取許可權被設定成了private級別

解決方案:

  • 加註解,@JsonProperty(“isEnd”)
  • 將方法級別更正為public

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com