java枚举类使用注解@JsonFormat(shape = JsonFormat.Shape.OBJECT)导致的反序列化问题的完美解决方案,尤其是openfeign和对象转换时

@JsonFormat(shape = JsonFormat.Shape.OBJECT)的作用

作用

这个注解的作用是将枚举类像对象一样序列化。

为什么使用?

如果不以这种方式,那么枚举类的值就仅仅是他的名字(枚举类自带的name字段),这样前后端协作时就不好知道你的枚举类都是什么含义,还要自己写额外的文档,一旦修改或者增加则还要修改文档,很不方便。

使用方式

定义这样一个接口,让需要以这种方式序列化的枚举类都实现这个接口即可

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {}

一般会这样定义接口:要求枚举类至少有个对其自身含义的解释

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {
    String desc();
}

然后枚举类的定义:

import lombok.Getter;

@Getter
public enum AnswerStatus implements BaseEnum {

    //正确
    CORRECT("正确"),
    //错误
    WRONG("错误"),
    //半正确
    PARTIAL("半对");

    private final String name;

    private final String desc;

    AnswerStatus(String desc) {
        this.name = this.name();
        this.desc = desc;
    }

    @Override
    public String desc() {
        return this.desc;
    }
}

如果写这样一个接口方法:

@GetMapping
public AnswerStatus[] getAnswerStatus() {
    return AnswerStatus.values();
}

其查询结果为:

[
    {
      "name": "CORRECT",
      "desc": "正确"
    },
    {
      "name": "WRONG",
      "desc": "错误"
    },
    {
      "name": "PARTIAL",
      "desc": "半对"
    },
    {
      "name": "UNRECOGNIZED",
      "desc": "未识别到"
    }
]

可以看到跟不使用这个注解,只有一个名字的查询结果很不一样

引发的问题

一定场景下的反序列化问题

序列化方便了,那么反序列化就会变的麻烦

枚举类的默认反序列化方法是接收枚举对象的名字来创建,显而易见如果你只是接收前端或者数据库(数据库枚举类存储也只是枚举对象的名字时)的数据,那么反序列化不会有任何问题。
但是,在有对象转化或者序列化传递时就会有问题,例如:open Feign中查询结果的反序列化。
因为序列化的是以对象的方式,反序列化会以Object的方式去寻找反序列化方法,结果当然是找不到。
需要在实现了BaseEnum的枚举类下实现自定义的@JsonCreator方法,同时BaseEnum也需要增加一些公共的方法:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {

    String desc();

    ObjectMapper objectMapper = new ObjectMapper();

    static <E extends Enum<E> & BaseEnum> E fromName(String desc, Class<E> enumClass) {
        for (E answerSheetOptionArrangement : enumClass.getEnumConstants()) {
            if (answerSheetOptionArrangement.name().equals(desc)) {
                return answerSheetOptionArrangement;
            }
        }
        return null;
    }

    static String getNameValue(String input) {

        if (input.startsWith("{") && input.endsWith("}")) {
            input = input.substring(1, input.length() - 1);
            String[] pairs = input.split(", ");
            for (String pair : pairs) {
                if (pair.startsWith("name=")) {
                    return pair.split("=")[1];
                }
            }
        }
        return null;
    }
}

自定义完整枚举类:

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;

@Getter
public enum AnswerStatus implements BaseEnum {

    //正确
    CORRECT("正确"),
    //错误
    WRONG("错误"),
    //半正确
    PARTIAL("半对"),
    //未识别
    UNRECOGNIZED("未识别到");

    private final String name;

    private final String desc;

    AnswerStatus(String desc) {
        this.name = name();
        this.desc = desc;
    }

    @Override
    public String desc() {
        return this.desc;
    }

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static AnswerStatus forNames(@JsonProperty Object object) {
        if (object == null) {
            return null;
        }

        String jsonStr = object.toString();

        return forNames(jsonStr);
    }

    public static AnswerStatus forNames(@JsonProperty String jsonStr) {
        if (jsonStr == null) {
            return null;
        }

        if (!JSON.isValid(jsonStr)) {
            if (jsonStr.contains("name=")) {
                return BaseEnum.fromName(BaseEnum.getNameValue(jsonStr), AnswerStatus.class);
            }
            return BaseEnum.fromName(jsonStr, AnswerStatus.class);
        }

        try {
            JSONObject jsonObject = JSON.parseObject(jsonStr);
            if (jsonObject.containsKey("name")) {
                return BaseEnum.fromName(jsonObject.getString("name"), AnswerStatus.class);
            }
        } catch (Exception ignore) {
            // ignore
        }

        return BaseEnum.fromName(jsonStr, AnswerStatus.class);
    }
}

很多报错都与反序列化方法考虑不全面导致的,例如:
1

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `xxx.xxx.AnswerStatus`: More than one argument (#0 and #1) left as delegating for Creator [method xxx.xxx.AnswerStatus#forObject(2 params)]: only one allowed
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]

2

 Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 117] (through reference chain: java.util.ArrayList[0]->xxx.Object["category"]->xxx.AnswerStatus["superCategory"])

3

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Input mismatch reading Enum `xxx.AnswerStatus`: properties-based `@JsonCreator` ([method xxx.AnswerStatus#forNames(java.lang.String)]) expects String Value, got Object value (`JsonToken.START_OBJECT`)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 77] (through reference chain: xxx.AnswerStatus["pattern"])

4

An unexpected error occurred: Error while extracting response for type [class xxx.AnswerStatus] and content type [application/json;charset=UTF-8]
feign.codec.DecodeException: Error while extracting response for type [class xxx.AnswerStatus] and content type [application/json;charset=UTF-8]

等等
都是@JsonCreator方法的问题

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值